diff --git a/.fern/metadata.json b/.fern/metadata.json new file mode 100644 index 0000000..8141518 --- /dev/null +++ b/.fern/metadata.json @@ -0,0 +1,13 @@ +{ + "cliVersion": "5.45.3", + "generatorName": "fernapi/fern-python-sdk", + "generatorVersion": "5.14.18", + "generatorConfig": { + "client_class_name": "Wavix" + }, + "originGitCommit": "1498ee51f17678c87170c7d44e0568dbebd562b6", + "originGitCommitIsDirty": false, + "invokedBy": "manual", + "requestedVersion": "1.0.0", + "sdkVersion": "1.0.0" +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..300abfb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: ci + +on: [push] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest -rP -n auto . + + - name: Install aiohttp extra + run: poetry install --extras aiohttp + + - name: Test (aiohttp) + run: poetry run pytest -rP -n auto -m aiohttp . + + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Build package + run: poetry build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish: + name: Publish to PyPI + needs: [compile, test, build] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/${{ github.event.repository.name }} + permissions: + contents: read # Required for checkout + id-token: write # Required for OIDC + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish to pypi with attestations + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://upload.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2e4ca8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.mypy_cache/ +.ruff_cache/ +__pycache__/ +dist/ +poetry.toml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..af948ce --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# Contributing + +Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project. + +## Getting Started + +### Prerequisites + +- Python 3.9+ +- pip +- poetry + +### Installation + +Install the project dependencies: + +```bash +poetry install +``` + +### Building + +Build the project: + +```bash +poetry build +``` + +### Testing + +Run the test suite: + +```bash +poetry run pytest +``` + +### Linting and Formatting + +Check code style: + +```bash +poetry run ruff check . +poetry run ruff format . +``` + +### Type Checking + +Run the type checker: + +```bash +poetry run mypy . +``` + +## About Generated Code + +**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated. + +### Generated Files + +The following directories contain generated code: +- `src/` - API client classes and types +- Most Python files in the project + +### How to Customize + +If you need to customize the SDK, you have two options: + +#### Option 1: Use `.fernignore` + +For custom code that should persist across SDK regenerations: + +1. Create a `.fernignore` file in the project root +2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax) +3. Add your custom code to those files + +Files listed in `.fernignore` will not be overwritten when the SDK is regenerated. + +For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code). + +#### Option 2: Contribute to the Generator + +If you want to change how code is generated for all users of this SDK: + +1. The Python SDK generator lives in the [Fern repository](https://github.com/fern-api/fern) +2. Generator code is located at `generators/python-v2/` +3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md) +4. Submit a pull request with your changes to the generator + +This approach is best for: +- Bug fixes in generated code +- New features that would benefit all users +- Improvements to code generation patterns + +## Making Changes + +### Workflow + +1. Create a new branch for your changes +2. Make your modifications +3. Run tests to ensure nothing breaks: `poetry run pytest` +4. Run linting and formatting: `poetry run ruff check .` and `poetry run ruff format .` +5. Run type checking: `poetry run mypy .` +6. Build the project: `poetry build` +7. Commit your changes with a clear commit message +8. Push your branch and create a pull request + +### Commit Messages + +Write clear, descriptive commit messages that explain what changed and why. + +### Code Style + +This project uses automated code formatting and linting. Run `poetry run ruff format .` and `poetry run ruff check .` before committing to ensure your code meets the project's style guidelines. + +## Questions or Issues? + +If you have questions or run into issues: + +1. Check the [Fern documentation](https://buildwithfern.com) +2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues) +3. Open a new issue if your question hasn't been addressed + +## License + +By contributing to this project, you agree that your contributions will be licensed under the same license as the project. diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bef81c0 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1469 @@ +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +description = "Happy Eyeballs for asyncio" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, +] + +[[package]] +name = "aiohttp" +version = "3.14.1" +description = "Async http client/server framework (asyncio)" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "aiohttp-3.14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e"}, + {file = "aiohttp-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491"}, + {file = "aiohttp-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb"}, + {file = "aiohttp-3.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d"}, + {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966"}, + {file = "aiohttp-3.14.1-cp310-cp310-win32.whl", hash = "sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6"}, + {file = "aiohttp-3.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df"}, + {file = "aiohttp-3.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c"}, + {file = "aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2"}, + {file = "aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f"}, + {file = "aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730"}, + {file = "aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85"}, + {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3"}, + {file = "aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126"}, + {file = "aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5"}, + {file = "aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b"}, + {file = "aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480"}, + {file = "aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d"}, + {file = "aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c"}, + {file = "aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087"}, + {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3"}, + {file = "aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4"}, + {file = "aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271"}, + {file = "aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847"}, + {file = "aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178"}, + {file = "aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf"}, + {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd"}, + {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe"}, + {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4"}, + {file = "aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876"}, + {file = "aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da"}, + {file = "aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451"}, + {file = "aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345"}, + {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588"}, + {file = "aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780"}, + {file = "aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a"}, + {file = "aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8"}, + {file = "aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15"}, + {file = "aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8"}, + {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3"}, + {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba"}, + {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397"}, + {file = "aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448"}, + {file = "aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004"}, + {file = "aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602"}, + {file = "aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95"}, + {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444"}, + {file = "aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0"}, + {file = "aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719"}, + {file = "aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec"}, + {file = "aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2"}, + {file = "aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340"}, + {file = "aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3"}, + {file = "aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d"}, + {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c"}, + {file = "aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365"}, + {file = "aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9"}, + {file = "aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6"}, + {file = "aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +typing_extensions = {version = ">=4.4", markers = "python_version < \"3.13\""} +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\" and sys_platform != \"android\" and sys_platform != \"ios\"", "aiodns (>=3.3.0) ; sys_platform != \"android\" and sys_platform != \"ios\"", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\" and sys_platform != \"android\" and sys_platform != \"ios\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.14.1" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72"}, + {file = "anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +trio = ["trio (>=0.32.0)"] + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.10\" and extra == \"aiohttp\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "26.1.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db"}, + {file = "certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "frozenlist" +version = "1.8.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, +] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "httpx-aiohttp" +version = "0.1.8" +description = "Aiohttp transport for HTTPX" +optional = true +python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"}, + {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"}, +] + +[package.dependencies] +aiohttp = ">=3.10.0,<4" +httpx = ">=0.27.0" + +[[package]] +name = "idna" +version = "3.18" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, + {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, +] + +[package.extras] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "multidict" +version = "6.7.1" +description = "multidict implementation" +optional = true +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "propcache" +version = "0.5.2" +description = "Accelerated property cache" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274"}, + {file = "propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe"}, + {file = "propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d"}, + {file = "propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f"}, + {file = "propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0"}, + {file = "propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82"}, + {file = "propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a"}, + {file = "propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031"}, + {file = "propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42"}, + {file = "propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4"}, + {file = "propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d"}, + {file = "propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757"}, + {file = "propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568"}, + {file = "propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191"}, + {file = "propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7"}, + {file = "propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4"}, + {file = "propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0"}, + {file = "propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c"}, + {file = "propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2"}, + {file = "propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821"}, + {file = "propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370"}, + {file = "propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6"}, + {file = "propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe"}, + {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"}, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.46.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "9.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1"}, + {file = "pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.4,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)", "sphinx-tabs (>=3.5)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "ruff" +version = "0.11.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, + {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, + {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, + {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, + {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, + {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, + {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, + {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, + {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tomli" +version = "2.4.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20260518" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "types_python_dateutil-2.9.0.20260518-py3-none-any.whl", hash = "sha256:d6a9c5bd0de61460c8fdef8ab2b400f956a1a1075cce08d4e2b4434e478c50b8"}, + {file = "types_python_dateutil-2.9.0.20260518.tar.gz", hash = "sha256:51f02dc03b61c7f6a07df45797d4dfe8a1aa47f0b7db9ad89f6fd3a1a70e1b51"}, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "urllib3" +version = "2.7.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + +[[package]] +name = "yarl" +version = "1.24.2" +description = "Yet another URL library" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "extra == \"aiohttp\"" +files = [ + {file = "yarl-1.24.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5249a113065c2b7a958bc699759e359cd61cfc81e3069662208f48f191b7ed12"}, + {file = "yarl-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4425fa244fbf530b006d0c5f79ce920114cfff5b4f5f6056e669f8e160fdc0"}, + {file = "yarl-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15c0b5e49d3c44e2a0b93e6a49476c5edad0a7686b92c395765a7ea775572a75"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:246d32a53a947c8f0189f5d699cbd4c7036de45d9359e13ba238d1239678c727"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:64480fb3e4d4ed9ed71c48a91a477384fc342a50ca30071d2f8a88d51d9c9413"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:349de4701dc3760b6e876628423a8f147ef4f5599d10aba1e10702075d424ed9"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d162677af8d5d3d6ebab8394b021f4d041ac107a4b705873148a77a49dc9e1b2"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5f5c6ec23a9043f2d139cc072f53dd23168d202a334b9b2fda8de4c3e890d90"}, + {file = "yarl-1.24.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:60de6742447fbbf697f16f070b8a443f1b5fe6ca3826fbef9fe70ecd5328e643"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acf93187c3710e422368eb768aee98db551ec7c85adc250207a95c16548ab7ac"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4b0352fd41fd34b6651934606268816afd6914d09626f9bcbbf018edb0afb3f"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6b208bb939099b4b297438da4e9b25357f0b1c791888669b963e45b203ea9f36"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4b85b8825e631295ff4bc8943f7471d54c533a9360bbe15ebb38e018b555bb8a"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e26acf20c26cb4fefc631fdb75aca2a6b8fa8b7b5d7f204fb6a8f1e63c706f53"}, + {file = "yarl-1.24.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:819ca24f8eafcfb683c1bd5f44f2f488cea1274eb8944731ffd2e1f10f619342"}, + {file = "yarl-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cb0f995a901c36be096ccbf4c673591c2faabbe96279598ffaec8c030f85bf4"}, + {file = "yarl-1.24.2-cp310-cp310-win_arm64.whl", hash = "sha256:f408eace7e22a68b467a0562e0d27d322f91fe3eaaa6f466b962c6cfaea9fa39"}, + {file = "yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1"}, + {file = "yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c"}, + {file = "yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003"}, + {file = "yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b"}, + {file = "yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5"}, + {file = "yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45"}, + {file = "yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122"}, + {file = "yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8"}, + {file = "yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2"}, + {file = "yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c"}, + {file = "yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c"}, + {file = "yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1"}, + {file = "yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad"}, + {file = "yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30"}, + {file = "yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536"}, + {file = "yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607"}, + {file = "yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617"}, + {file = "yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056"}, + {file = "yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992"}, + {file = "yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656"}, + {file = "yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461"}, + {file = "yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2"}, + {file = "yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630"}, + {file = "yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208"}, + {file = "yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b"}, + {file = "yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8"}, + {file = "yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0"}, + {file = "yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57"}, + {file = "yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b"}, + {file = "yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761"}, + {file = "yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf"}, + {file = "yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe"}, + {file = "yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd"}, + {file = "yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215"}, + {file = "yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d"}, + {file = "yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9"}, + {file = "yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + +[extras] +aiohttp = ["aiohttp", "httpx-aiohttp"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "8a2af44ec07cc26b89a775d8383a2fbf2203875bc97f422dec79ecd475a30518" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..710e2c5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,97 @@ +[project] +name = "wavix-python-sdk" +dynamic = ["version"] + +[tool.poetry] +name = "wavix-python-sdk" +version = "1.0.0" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "wavix", from = "src"} +] + +[tool.poetry.urls] +Repository = 'https://github.com/wavix/wavix-python-sdk' + +[tool.poetry.dependencies] +python = "^3.10" +aiohttp = { version = ">=3.14.0,<4", optional = true, python = ">=3.10"} +httpx = ">=0.21.2" +httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} +pydantic = ">= 1.9.2" +pydantic-core = ">=2.18.2,<3.0.0" +typing_extensions = ">= 4.0.0" + +[tool.poetry.group.dev.dependencies] +mypy = "==1.13.0" +pytest = "^9.0.3" +pytest-asyncio = "^1.0.0" +pytest-xdist = "^3.6.1" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" +urllib3 = ">=2.6.3,<3.0.0" +ruff = "==0.11.5" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" +norecursedirs = [ "src" ] +markers = [ + "aiohttp: tests that require httpx_aiohttp to be installed", +] + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort +] +ignore = [ + "E402", # Module level import not at top of file + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is not None` + "E712", # Avoid equality comparisons to `True`; use `if ...:` checks + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` for insinstance checks + "E722", # Do not use bare `except` + "E731", # Do not assign a `lambda` expression, use a `def` + "F821", # Undefined name + "F841" # Local variable ... is assigned to but never used +] + +[tool.ruff.lint.isort] +section-order = ["future", "standard-library", "third-party", "first-party"] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.extras] +aiohttp=["aiohttp", "httpx-aiohttp"] diff --git a/reference.md b/reference.md new file mode 100644 index 0000000..b13d80f --- /dev/null +++ b/reference.md @@ -0,0 +1,11938 @@ +# Reference +## API Keys +
client.api_keys.list(...) -> typing.List[ApiKey] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the API keys belonging to the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.api_keys.list( + label="production", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**label:** `typing.Optional[str]` — Filters API keys by `label`. Matches partial values. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.api_keys.create(...) -> ApiKey +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates an API key for the authenticated account. Restrict access by listing permitted IP addresses in `permitted_ips`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix, ApiKeyScopePermission, ApiKeyCallsScopePermission +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.api_keys.create( + label="Production API Key", + active=True, + restricted=True, + permitted_ips=[ + "192.168.1.1", + "10.0.0.1" + ], + scopes_enabled=True, + numbers=ApiKeyScopePermission( + allow="read", + ), + calls=ApiKeyCallsScopePermission( + allow="read", + ), + messages=ApiKeyScopePermission( + allow="write", + ), + two_fa=ApiKeyScopePermission( + allow="write", + ), + billing=ApiKeyScopePermission( + allow="read", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**label:** `str` — API key label. + +
+
+ +
+
+ +**active:** `typing.Optional[bool]` — Indicates whether the API key should be activated upon creation. + +
+
+ +
+
+ +**restricted:** `typing.Optional[bool]` — Indicates whether to restrict API key access by IP address. When enabled, only requests from IP addresses listed in `permitted_ips` are allowed. + +
+
+ +
+
+ +**permitted_ips:** `typing.Optional[typing.List[str]]` — List of permitted IP addresses for this API key. Each must be a valid IPv4 address. Required when `restricted` is true. + +
+
+ +
+
+ +**scopes_enabled:** `typing.Optional[bool]` + +When `true`, scope fields below are enforced. When `false` (default), the +key has full access. Omitted scope fields default to `{ allow: none }`, +so with `scopes_enabled: true` and no scopes set the key has no access. + +
+
+ +
+
+ +**numbers:** `typing.Optional[ApiKeyScopePermission]` — View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + +
+
+ +
+
+ +**trunks:** `typing.Optional[ApiKeyScopePermission]` — View, create, update, and delete SIP trunks and their settings. + +
+
+ +
+
+ +**calls:** `typing.Optional[ApiKeyCallsScopePermission]` — Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + +
+
+ +
+
+ +**messages:** `typing.Optional[ApiKeyScopePermission]` — Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + +
+
+ +
+
+ +**recordings:** `typing.Optional[ApiKeyScopePermission]` — List, download, and delete call recordings. + +
+
+ +
+
+ +**campaigns:** `typing.Optional[ApiKeyScopePermission]` — View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + +
+
+ +
+
+ +**two_fa:** `typing.Optional[ApiKeyScopePermission]` — View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + +
+
+ +
+
+ +**validator:** `typing.Optional[ApiKeyScopePermission]` — View number validation results and trigger single or bulk validation or HLR lookup requests. + +
+
+ +
+
+ +**webhooks:** `typing.Optional[ApiKeyScopePermission]` — List, create, and delete webhooks. + +
+
+ +
+
+ +**embeddable:** `typing.Optional[ApiKeyScopePermission]` — Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + +
+
+ +
+
+ +**billing:** `typing.Optional[ApiKeyScopePermission]` — Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + +
+
+ +
+
+ +**account:** `typing.Optional[ApiKeyScopePermission]` — View and update account profile information and timezone. + +
+
+ +
+
+ +**subaccounts:** `typing.Optional[ApiKeyScopePermission]` — Manage subaccounts: list and view them, create, update, and suspend them. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.api_keys.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the API key identified by `id`. Deletion is permanent and revokes the key immediately. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.api_keys.delete( + id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the API key. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.api_keys.update(...) -> ApiKey +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates an API key identified by `id`. Only the provided fields are changed. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.api_keys.update( + id=1, + active=True, + restricted=True, + scopes_enabled=True, + permitted_ips=[ + "192.168.1.1", + "10.0.0.1" + ], + label="Production API Key", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the API key. + +
+
+ +
+
+ +**active:** `typing.Optional[bool]` — Indicates whether the API key is active. + +
+
+ +
+
+ +**restricted:** `typing.Optional[bool]` — Indicates whether the API key is restricted to the listed permitted IPs. + +
+
+ +
+
+ +**scopes_enabled:** `typing.Optional[bool]` — Indicates whether per-resource scope permissions are enforced for the API key. + +
+
+ +
+
+ +**permitted_ips:** `typing.Optional[typing.List[str]]` — IP addresses allowed to use the API key when restriction is enabled. + +
+
+ +
+
+ +**label:** `typing.Optional[str]` — Human-readable label for the API key. + +
+
+ +
+
+ +**numbers:** `typing.Optional[ApiKeyScopePermission]` — View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + +
+
+ +
+
+ +**trunks:** `typing.Optional[ApiKeyScopePermission]` — View, create, update, and delete SIP trunks and their settings. + +
+
+ +
+
+ +**calls:** `typing.Optional[ApiKeyCallsScopePermission]` — Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + +
+
+ +
+
+ +**messages:** `typing.Optional[ApiKeyScopePermission]` — Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + +
+
+ +
+
+ +**recordings:** `typing.Optional[ApiKeyScopePermission]` — List, download, and delete call recordings. + +
+
+ +
+
+ +**campaigns:** `typing.Optional[ApiKeyScopePermission]` — View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + +
+
+ +
+
+ +**two_fa:** `typing.Optional[ApiKeyScopePermission]` — View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + +
+
+ +
+
+ +**validator:** `typing.Optional[ApiKeyScopePermission]` — View number validation results and trigger single or bulk validation or HLR lookup requests. + +
+
+ +
+
+ +**webhooks:** `typing.Optional[ApiKeyScopePermission]` — List, create, and delete webhooks. + +
+
+ +
+
+ +**embeddable:** `typing.Optional[ApiKeyScopePermission]` — Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + +
+
+ +
+
+ +**billing:** `typing.Optional[ApiKeyScopePermission]` — Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + +
+
+ +
+
+ +**account:** `typing.Optional[ApiKeyScopePermission]` — View and update account profile information and timezone. + +
+
+ +
+
+ +**subaccounts:** `typing.Optional[ApiKeyScopePermission]` — Manage subaccounts: list and view them, create, update, and suspend them. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SIP trunks +
client.sip_trunks.list(...) -> SipTrunkListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of SIP trunks for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sip_trunks.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sip_trunks.create(...) -> SipTrunkResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a SIP trunk for routing inbound and outbound calls. Returns the trunk with its generated `access_token`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sip_trunks.create( + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SipTrunkCreateRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sip_trunks.get(...) -> SipTrunkResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the SIP trunk identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sip_trunks.get( + id=3107, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the SIP trunk. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sip_trunks.update(...) -> SipTrunkResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Replaces the configuration of the SIP trunk identified by `id`. Omitted fields revert to their defaults. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sip_trunks.update( + id=3107, + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the SIP trunk. + +
+
+ +
+
+ +**request:** `SipTrunkCreateRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sip_trunks.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the SIP trunk identified by `id`. Deletion is permanent and stops call routing through the trunk. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sip_trunks.delete( + id=3107, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the SIP trunk. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Cart +
client.cart.get() -> GetCartResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the current purchase cart, including the phone numbers it contains and the documents each requires. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cart.get() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cart.add(...) -> typing.List[typing.Any] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Adds the listed phone numbers to the purchase cart. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cart.add( + ids=[ + "541139862174", + "541139862175" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `typing.List[str]` — Phone numbers to add to the cart. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cart.remove(...) -> RemoveCartResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Removes the listed phone numbers from the purchase cart. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cart.remove( + ids=[ + "541139862174", + "541139862175" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `typing.List[str]` — Phone numbers to remove from the cart. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cart.checkout(...) -> CheckoutCartResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Purchases the listed phone numbers from the cart. Activation and monthly fees are deducted from the account balance. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cart.checkout( + ids=[ + "541139862174", + "541139862175" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `typing.List[str]` — Phone numbers from the cart to purchase. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Numbers +
client.numbers.list(...) -> NumberListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of the phone numbers owned by the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.list( + city_id=123, + search="256537", + label="ALEX", + label_present=True, + page=2, + per_page=50, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**city_id:** `typing.Optional[int]` — Filters numbers by the ID of their city or rate center. + +
+
+ +
+
+ +**search:** `typing.Optional[str]` — Filters numbers by a full or partial phone number. + +
+
+ +
+
+ +**label:** `typing.Optional[str]` — Filters numbers by `label`. + +
+
+ +
+
+ +**label_present:** `typing.Optional[bool]` — When `true`, returns only numbers that have a label; when `false`, only numbers without one. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.numbers.delete(...) -> DeleteNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Releases the listed phone numbers back to stock. Selection accepts either `ids` (record IDs) or `dids` (phone numbers), but not both. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.delete( + dids="47832123321,47832123324,478321233215", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `typing.Optional[typing.Union[int, typing.Sequence[int]]]` — Record IDs of the phone numbers to release. Mutually exclusive with `dids`. + +
+
+ +
+
+ +**dids:** `typing.Optional[str]` — Comma-separated phone numbers to release. Mutually exclusive with `ids`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.numbers.bulk_update(...) -> BulkUpdateNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Applies the same changes to every listed phone number. Only the provided fields are changed. Destination and SMS callback changes are applied asynchronously and may not be reflected in the response immediately. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.bulk_update( + ids=[ + 123, + 456 + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**ids:** `typing.List[int]` + +Numbers (by ID) to apply the patch to. The same patch is +applied to every listed number. + +
+
+ +
+
+ +**sms_enabled:** `typing.Optional[bool]` — Indicates whether SMS is enabled for the phone numbers. + +
+
+ +
+
+ +**destinations:** `typing.Optional[typing.List[NumberDestination]]` — Inbound call routing destinations for the phone numbers. + +
+
+ +
+
+ +**sms_relay_url:** `typing.Optional[str]` — Callback URL for inbound messages. + +
+
+ +
+
+ +**call_recording_enabled:** `typing.Optional[bool]` — Indicates whether call recording is enabled. + +
+
+ +
+
+ +**transcription_enabled:** `typing.Optional[bool]` — Indicates whether call transcription is enabled. + +
+
+ +
+
+ +**transcription_threshold:** `typing.Optional[int]` — Minimum call duration in seconds before transcription runs. + +
+
+ +
+
+ +**call_status_url:** `typing.Optional[str]` — Callback URL for call status updates. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.numbers.get(...) -> Number +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the phone number identified by `id`, including its destinations, documents, and feature settings. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.get( + id=123, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the phone number. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.numbers.update(...) -> Number +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the phone number identified by `id`. Only the provided fields are changed. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.update( + id=123, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the phone number. + +
+
+ +
+
+ +**sms_enabled:** `typing.Optional[bool]` — Indicates whether SMS is enabled for the phone number. + +
+
+ +
+
+ +**destinations:** `typing.Optional[typing.List[NumberDestination]]` — Inbound call routing destinations for the phone number. + +
+
+ +
+
+ +**sms_relay_url:** `typing.Optional[str]` — Callback URL for inbound messages. + +
+
+ +
+
+ +**call_recording_enabled:** `typing.Optional[bool]` — Indicates whether call recording is enabled. + +
+
+ +
+
+ +**transcription_enabled:** `typing.Optional[bool]` — Indicates whether call transcription is enabled. + +
+
+ +
+
+ +**transcription_threshold:** `typing.Optional[int]` — Minimum call duration in seconds before transcription runs. + +
+
+ +
+
+ +**call_status_url:** `typing.Optional[str]` — Callback URL for call status updates. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## CDRs +
client.cdrs.list(...) -> CdrListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of call detail records for the authenticated account, within the requested date range. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.list( + from_=datetime.date.fromisoformat("2023-01-01"), + to=datetime.date.fromisoformat("2023-09-01"), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**type:** `str` — Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + +
+
+ +
+
+ +**disposition:** `typing.Optional[CallDisposition]` — Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + +
+
+ +
+
+ +**from_search:** `typing.Optional[str]` — Filters CDRs by originating phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**to_search:** `typing.Optional[str]` — Filters CDRs by destination phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**sip_trunk:** `typing.Optional[str]` — Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + +
+
+ +
+
+ +**uuid:** `typing.Optional[str]` — Filters CDRs by the unique call ID. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cdrs.search(...) -> CdrTranscriptionSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Searches call transcriptions for the given keywords or phrases and returns the matching CDRs with their transcriptions. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.search( + type="placed", + from_=datetime.date.fromisoformat("2023-08-01"), + to=datetime.date.fromisoformat("2023-08-31"), + page=1, + per_page=50, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**type:** `CdrSearchRequestType` — Filters by call type. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + +
+
+ +
+
+ +**from:** `datetime.date` — Start date for call search in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**to:** `datetime.date` — End date for call search in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**page:** `int` — Page number to retrieve. + +
+
+ +
+
+ +**per_page:** `int` — Number of records per page. + +
+
+ +
+
+ +**from_search:** `typing.Optional[str]` — Originating phone number to filter results. Accepts full or partial number. + +
+
+ +
+
+ +**to_search:** `typing.Optional[str]` — Destination phone number to filter results. Accepts full or partial number. + +
+
+ +
+
+ +**sip_trunk:** `typing.Optional[str]` — SIP trunk login to filter outbound calls. Ignored for inbound calls. + +
+
+ +
+
+ +**min_duration:** `typing.Optional[int]` — Minimum call duration in seconds. + +
+
+ +
+
+ +**transcription:** `typing.Optional[TranscriptionFilter]` + +
+
+ +
+
+ +**uuid:** `typing.Optional[str]` — Call ID. + +
+
+ +
+
+ +**disposition:** `typing.Optional[CdrSearchRequestDisposition]` + +Call disposition to filter results. If omitted, returns only answered + calls. Allowed values: `answered`, `busy`, `rejected`, + `failed`, `all`. Use `all` to return calls + regardless of their disposition. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cdrs.retranscribe(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Transcribes the recording of the call identified by `call_id`. Transcription is asynchronous; poll the transcription endpoint for the result. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.retranscribe( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The unique ID of the call. + +
+
+ +
+
+ +**language:** `typing.Optional[TranscriptionLanguage]` + +
+
+ +
+
+ +**webhook_url:** `typing.Optional[str]` — Webhook URL to receive status updates. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cdrs.transcriptions(...) -> CdrTranscriptionResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the transcription of the recorded call identified by `call_id`. Alias of the `transcription` endpoint. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.transcriptions( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The unique ID of the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cdrs.get(...) -> CdrResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the call detail record for the call identified by `call_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.get( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", + show_transcription=True, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The unique ID of the call. + +
+
+ +
+
+ +**show_transcription:** `typing.Optional[bool]` — When `true`, includes the call transcription in the response. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.cdrs.list_all(...) -> str +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Streams matching call detail records as newline-delimited JSON (NDJSON), one record per line, for bulk export. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.list_all( + from_=datetime.date.fromisoformat("2023-01-01"), + to=datetime.date.fromisoformat("2023-09-01"), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**type:** `str` — Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + +
+
+ +
+
+ +**disposition:** `typing.Optional[CallDisposition]` — Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + +
+
+ +
+
+ +**from_search:** `typing.Optional[str]` — Filters CDRs by originating phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**to_search:** `typing.Optional[str]` — Filters CDRs by destination phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**sip_trunk:** `typing.Optional[str]` — Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + +
+
+ +
+
+ +**uuid:** `typing.Optional[str]` — Filters CDRs by the unique call ID. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Call recording +
client.call_recording.list(...) -> CallRecordingListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of call recordings for the authenticated account, filtered by date range, number, call, or SIP trunk. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_recording.list( + from_date=datetime.date.fromisoformat("2023-01-01"), + to_date=datetime.date.fromisoformat("2023-12-31"), + from_="123456", + to="1987654321", + call_uuid="aa566501-c591-4a8b-b3b9-cc1295398b72", + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from_date:** `typing.Optional[datetime.date]` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to_date:** `typing.Optional[datetime.date]` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**from:** `typing.Optional[str]` — Filters recordings by originating phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**to:** `typing.Optional[str]` — Filters recordings by destination phone number. Accepts a full or partial number. + +
+
+ +
+
+ +**call_uuid:** `typing.Optional[str]` — Filters recordings by the unique call ID. + +
+
+ +
+
+ +**sip_trunks:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Filters recordings of outbound calls placed through the listed SIP trunk logins. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_recording.get_by_call(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Redirects to the recording file for the call identified by `call_id`. The download URL is returned in the `Location` header. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_recording.get_by_call( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The unique ID of the call whose recording is retrieved. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_recording.get(...) -> Recording +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the call recording identified by `id`, including its metadata and download URL. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_recording.get( + id=123, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the call recording. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_recording.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the call recording identified by `id`. Deletion is permanent and removes the recording file. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_recording.delete( + id=123, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the call recording. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Speech Analytics +
client.speech_analytics.create(...) -> CreateSpeechAnalyticsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Uploads an audio file for transcription. Transcription is asynchronous; Wavix sends a POST callback to `callback_url` when it completes, including the `request_id` returned by this request. + +Callback body: +```json + { + "request_id": "e865ea07-25af-4fdd-876e-04b0d41d5ebd", + "status": "completed", + "error": null + } +``` + +- `request_id`: ID of the transcription request. +- `status`: One of `completed` (transcription succeeded) or `failed` (transcription encountered an error). +- `error`: Error description, or `null` when the transcription succeeded. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.speech_analytics.create( + file="example_file", + callback_url="callback_url", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**file:** `core.File` — Audio file to transcribe. Maximum size is 25 MB. Supported formats are WAV, MP3, and MP4 stereo. + +
+
+ +
+
+ +**callback_url:** `str` — URL that receives the POST callback when transcription completes. + +
+
+ +
+
+ +**insights:** `typing.Optional[bool]` — When `true`, generates conversation insights alongside the transcript. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.speech_analytics.get(...) -> GetSpeechAnalyticsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the transcription for the request identified by `request_id`, including transcript, speaker turns, and insights when available. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.speech_analytics.get( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_id:** `str` — The `request_id` of the transcription, returned when the file was uploaded. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.speech_analytics.retranscribe(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Re-runs transcription on the file identified by `request_id`, replacing the existing transcript. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.speech_analytics.retranscribe( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", + callback_url="https://you-site.com/webhook", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_id:** `str` — The `request_id` of the transcription, returned when the file was uploaded. + +
+
+ +
+
+ +**callback_url:** `str` — Callback URL for transcription status updates. + +
+
+ +
+
+ +**insights:** `typing.Optional[bool]` — Indicates whether to enable insights generation. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Call webhooks +
client.call_webhooks.list() -> CallWebhookListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the configured call webhooks for the authenticated account. Wavix sends POST callbacks for `on-call` and `post-call` events. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_webhooks.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_webhooks.create(...) -> CallWebhook +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Registers a callback URL for the `on-call` or `post-call` event. Wavix sends a POST callback to the URL when the event occurs. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_webhooks.create( + url="https://you-site.com/webhook", + event_type="post-call", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**url:** `str` — Webhook URL to send call events to. + +
+
+ +
+
+ +**event_type:** `CallWebhooksCreateRequestEventType` + +Allowed values: `on-call`, `post-call`. + - `on-call`: Sends real-time status updates + when a call starts, is answered, and ends. + + - `post-call`: Sends a callback after the call ends + with disposition, duration, and cost. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_webhooks.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Removes the call webhook for the given event type. Wavix stops sending callbacks for that event. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_webhooks.delete( + event_type="post-call", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**event_type:** `DeleteCallWebhooksRequestEventType` — Event type of the webhook to delete. One of `post-call` (callbacks sent after a call ends) or `on-call` (real-time call status callbacks during a call). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Call control +
client.call_control.list() -> CallListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the calls currently in progress for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.create(...) -> CallCreateResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Places an outbound call. Returns the call with its `uuid` for tracking and control. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.create( + from_="+1234567890", + to="+1987654321", + callback_url="https://examples.com/callback", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from:** `str` — Caller ID. Must be an active or verified phone number on the account. + +
+
+ +
+
+ +**to:** `str` — Destination number in E.164 format + +
+
+ +
+
+ +**callback_url:** `str` — The callback URL where Wavix sends the call status updates + +
+
+ +
+
+ +**recording:** `typing.Optional[bool]` — Specifies whether to record the call + +
+
+ +
+
+ +**voicemail_detection:** `typing.Optional[bool]` — Specifies whether the AMD is turned on for the call + +
+
+ +
+
+ +**tag:** `typing.Optional[str]` — Call metadata + +
+
+ +
+
+ +**timeout:** `typing.Optional[int]` — The ring timeout, in seconds, before the call is considered unanswered. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.get(...) -> CallResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the call identified by `id`, including its current event and timestamps. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.get( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Ends the active call identified by `id` by hanging up. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.delete( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.update(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the active call identified by `id`. Only the `tag` field can be changed. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.update( + id="id", + tag="marketing-campaign", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**tag:** `str` — User-defined label attached to the Call for tracking or reporting. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.answer(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Answers the inbound call identified by `id`. Optionally starts media streaming on answer. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.answer( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**call_recording:** `typing.Optional[bool]` — Indicates whether the call should be recorded. + +
+
+ +
+
+ +**call_transcription:** `typing.Optional[bool]` — Indicates whether the call should be transcribed after it ends. + +
+
+ +
+
+ +**stream_url:** `typing.Optional[str]` — WebSocket URL to stream the call. + +
+
+ +
+
+ +**stream_type:** `typing.Optional[CallStreamType]` — Direction of audio streamed to `stream_url`. + +
+
+ +
+
+ +**stream_channel:** `typing.Optional[CallStreamChannel]` — Audio channel streamed to `stream_url`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.collect(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Collects DTMF keypad input from the caller on the active call identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.collect( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**max_digits:** `typing.Optional[int]` — Maximum number of digits to collect. + +
+
+ +
+
+ +**timeout:** `typing.Optional[int]` — Timeout for digit collection in seconds. + +
+
+ +
+
+ +**termination_character:** `typing.Optional[str]` — DTMF character that ends input collection. + +
+
+ +
+
+ +**max_attempts:** `typing.Optional[int]` — Maximum number of attempts. + +
+
+ +
+
+ +**prompt:** `typing.Optional[CallDtmfCollectRequestPrompt]` + +Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## NumberValidator +
client.number_validator.get(...) -> GetNumberValidatorResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Validates a single phone number and returns line type, carrier, portability, and reachability details. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.number_validator.get( + phone_number="971569483322", + type="format", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**phone_number:** `str` — The phone number to validate, in E.164 format with or without the leading `+`. + +
+
+ +
+
+ +**type:** `PhoneNumberValidationType` — Depth of validation to perform. Accepts a `PhoneNumberValidationType` value. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.number_validator.create_bulk(...) -> NumberValidatorCreateBulkResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Validates a batch of phone numbers. When `async` is `true`, returns a `request_id` to poll for results instead of the validation details. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.number_validator.create_bulk( + phone_numbers=[ + "971501390098", + "971504359195" + ], + type="format", + async_=True, + force=True, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**phone_numbers:** `typing.List[str]` — List of phone numbers to get detailed information about. + +
+
+ +
+
+ +**type:** `PhoneNumberValidationType` + +
+
+ +
+
+ +**async:** `bool` — Indicates whether the request should be executed asynchronously. If `true`, the response will include a `request_uuid` that can be used to poll for results. If `false`, the response will include validation results directly. + +
+
+ +
+
+ +**force:** `bool` — Indicates whether to force a fresh validation instead of returning a previously cached result. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Voice campaigns +
client.voice_campaigns.create(...) -> VoiceCampaignsCreateResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Launches a voice campaign that places an outbound call using a pre-configured scenario. Track progress with the returned voice campaign `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix, VoiceCampaignResponse +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.voice_campaigns.create( + voice_campaign=VoiceCampaignResponse( + callflow_id=3212, + caller_id="13123310912", + contact="16729923812", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**voice_campaign:** `VoiceCampaignResponse` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.voice_campaigns.get(...) -> VoiceCampaignsGetResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the voice campaign identified by `id`, including its current status. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.voice_campaigns.get( + id=2321423, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the voice campaign to retrieve. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Link shortener +
client.link_shortener.create(...) -> ShortLinkResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a short link that redirects to the target URL and tracks click metrics. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.link_shortener.create( + link="https://your-site.com/long-url", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**link:** `str` — Target URL to shorten. + +
+
+ +
+
+ +**expiration_time:** `typing.Optional[datetime.datetime]` — Expiration date and time in ISO 8601 format. + +
+
+ +
+
+ +**fallback_url:** `typing.Optional[str]` — Fallback URL for expired or invalid links. + +
+
+ +
+
+ +**phone:** `typing.Optional[str]` — Phone number for the short link. + +
+
+ +
+
+ +**utm_campaign:** `typing.Optional[str]` — UTM campaign name for tracking insights. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Profile +
client.profile.get() -> ProfileResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the profile and billing details of the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.profile.get() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.profile.update(...) -> ProfileResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the profile and billing details of the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.profile.update() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**additional_info:** `typing.Optional[str]` — Additional information associated with the account. + +
+
+ +
+
+ +**contacts:** `typing.Optional[str]` — Email associated with the account. + +
+
+ +
+
+ +**default_short_link_endpoint:** `typing.Optional[str]` — Default short link endpoint. + +
+
+ +
+
+ +**first_name:** `typing.Optional[str]` — Account owner's first name. + +
+
+ +
+
+ +**last_name:** `typing.Optional[str]` — Account owner's last name. + +
+
+ +
+
+ +**phone:** `typing.Optional[str]` — Account owner's phone number + +
+
+ +
+
+ +**sms_relay_url:** `typing.Optional[str]` — Callback URL to forward inbound SMS to. + +
+
+ +
+
+ +**dlr_relay_url:** `typing.Optional[str]` — Callback URL to forward message delivery reports (DLRs) to. + +
+
+ +
+
+ +**time_zone:** `typing.Optional[str]` — Timezone configured on the account. + +
+
+ +
+
+ +**job_title:** `typing.Optional[str]` — Account owner's job title. + +
+
+ +
+
+ +**company_info:** `typing.Optional[ProfileUpdateRequestCompanyInfo]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SubAccounts +
client.sub_accounts.list(...) -> SubAccountsListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of sub-accounts under the authenticated master account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sub_accounts.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**status:** `typing.Optional[ListSubAccountsRequestStatus]` — Filters sub-accounts by status. One of `enabled` (the sub-account is active) or `disabled` (the sub-account is suspended). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sub_accounts.create(...) -> SubOrganizationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a sub-account under the authenticated master account. Returns the sub-account with its generated `api_key`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +from wavix.sub_accounts import SubAccountsCreateRequestDefaultDestinations + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sub_accounts.create( + name="Company", + default_destinations=SubAccountsCreateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `str` — Sub-account name. + +
+
+ +
+
+ +**default_destinations:** `typing.Optional[SubAccountsCreateRequestDefaultDestinations]` — Default webhook URLs for inbound messages and delivery reports. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sub_accounts.get(...) -> SubOrganizationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the sub-account identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sub_accounts.get( + id=123, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the sub-account. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sub_accounts.update(...) -> SubOrganizationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Replaces the configuration of the sub-account identified by `id`. Omitted fields revert to their defaults. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +from wavix.sub_accounts import SubAccountsUpdateRequestDefaultDestinations + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sub_accounts.update( + id=123, + name="Updated Company Name", + status="enabled", + default_destinations=SubAccountsUpdateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the sub-account. + +
+
+ +
+
+ +**name:** `str` — Sub-account name. + +
+
+ +
+
+ +**status:** `typing.Optional[SubAccountsUpdateRequestStatus]` — Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + +
+
+ +
+
+ +**default_destinations:** `typing.Optional[SubAccountsUpdateRequestDefaultDestinations]` — Default webhook URLs for inbound messages and delivery reports. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Billing Transactions +
client.billing.transactions.list(...) -> ListTransactionsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of billing transactions for the authenticated account within the requested date range. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.billing.transactions.list( + from_date=datetime.date.fromisoformat("2023-08-01"), + to_date=datetime.date.fromisoformat("2023-08-31"), + details_contains="monthly", + payments=True, + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from_date:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to_date:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**type:** `typing.Optional[TransactionType]` — Filters transactions by type. Accepts a `TransactionType` value. + +
+
+ +
+
+ +**details_contains:** `typing.Optional[str]` — Filters transactions whose `details` contain the given substring. + +
+
+ +
+
+ +**payments:** `typing.Optional[bool]` — When `true`, returns only account top-up transactions. Defaults to all transaction types. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Billing Invoices +
client.billing.invoices.list(...) -> ListInvoicesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the auto-generated financial statements for the authenticated account, paginated and ordered by billing period. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.billing.invoices.list( + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.billing.invoices.download(...) -> typing.Iterator[bytes] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the financial statement identified by `id` as a PDF file. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.billing.invoices.download( + id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the financial statement to download. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Buy Countries +
client.buy.countries.list(...) -> ListCountriesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a list of countries where phone numbers are available. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.buy.countries.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**text_enabled_only:** `typing.Optional[bool]` — When `true`, returns only countries that offer text-enabled phone numbers. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Buy Regions +
client.buy.regions.list(...) -> ListRegionsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a list of regions (states or provinces) for countries where `has_provinces_or_states` is `true`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.buy.regions.list( + country_id=1892, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**country_id:** `int` — The unique ID of the country. + +
+
+ +
+
+ +**text_enabled_only:** `typing.Optional[bool]` — When `true`, returns only regions that offer text-enabled numbers. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Buy Cities +
client.buy.cities.list(...) -> ListCitiesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a list of cities for countries where + `has_provinces_or_states` is `false`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.buy.cities.list( + country_id=1891, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**country_id:** `int` — The unique ID of the country. + +
+
+ +
+
+ +**text_enabled_only:** `typing.Optional[bool]` — When `true`, returns only cities that offer text-enabled numbers. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Buy RegionCities +
client.buy.region_cities.list(...) -> ListRegionCitiesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a list of cities in the specified region for countries where `has_provinces_or_states` is `true`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.buy.region_cities.list( + country_id=1891, + region_id=821, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**country_id:** `int` — The unique ID of the country. + +
+
+ +
+
+ +**region_id:** `int` — The unique ID of the region. + +
+
+ +
+
+ +**text_enabled_only:** `typing.Optional[bool]` — When `true`, returns only cities that offer text-enabled numbers. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Buy Numbers +
client.buy.numbers.list(...) -> ListNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of phone numbers available for purchase in the specified city. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.buy.numbers.list( + country_id=1, + city_id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**country_id:** `int` — The unique ID of the country. + +
+
+ +
+
+ +**city_id:** `int` — The unique ID of the city. + +
+
+ +
+
+ +**text_enabled_only:** `typing.Optional[bool]` — When `true`, returns only text-enabled phone numbers. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## CallControl Streams +
client.call_control.streams.create(...) -> CallStreamResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Starts streaming the media of the call identified by `call_id` to the configured destination. Returns the `stream_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.streams.create( + call_id="call_id", + stream_url="wss://examples.com/stream", + stream_type="oneway", + stream_channel="inbound", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**stream_url:** `str` — WebSocket URL for call streaming + +
+
+ +
+
+ +**stream_type:** `CallStreamType` — Direction of audio streamed to `stream_url`. + +
+
+ +
+
+ +**stream_channel:** `CallStreamChannel` — Audio channel streamed to `stream_url`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.streams.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Stops the media stream identified by `id` on the call identified by `call_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.streams.delete( + call_id="call_id", + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the media stream to stop. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## CallControl Audio +
client.call_control.audio.play(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Plays an audio prompt into the active call identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.audio.play( + id="id", + audio_file="https://examples.com/audio.wav", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**audio_file:** `str` — URL of the audio file to play to the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.call_control.audio.stop(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Stops audio playback in the active call identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.call_control.audio.stop( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The `uuid` of the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Cdrs Transcription +
client.cdrs.transcription.get(...) -> CdrTranscriptionResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the transcription of the recorded call identified by `call_id`, including the transcript, speaker turns, and summary. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.cdrs.transcription.get( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**call_id:** `str` — The unique ID of the call. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## LinkShortener Metrics +
client.link_shortener.metrics.list(...) -> ShortLinkMetricsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns per-click metrics for short links, including device, location, and campaign attribution, within the requested date range. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.link_shortener.metrics.list( + from_=datetime.date.fromisoformat("2023-05-01"), + to=datetime.date.fromisoformat("2023-05-31"), + phone="1872025555", + utm_campaign="summer", + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**phone:** `typing.Optional[str]` — Filters metrics by the phone number associated with the click, in E.164 format. + +
+
+ +
+
+ +**utm_campaign:** `typing.Optional[str]` — Filters metrics by `utm_campaign` name. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## NumberValidator Results +
client.number_validator.results.get(...) -> PhoneValidationBatchResultResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the results of an asynchronous batch validation identified by `request_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.number_validator.results.get( + request_id="12542c5c-1a17-4d12-a163-5b68543e75f6", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_id:** `str` — The `request_id` returned by the asynchronous bulk validation request. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Numbers Papers +
client.numbers.papers.upload(...) -> typing.List[NumberDocument] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Uploads a verification document for one or more phone numbers. +Uploaded files must meet the following requirements: +- Allowed formats: PNG, JPG, JPEG, TIFF, BMP, or PDF +- Maximum file size: 10 MB +- Files can't be password protected +- PDF files must not contain digital signatures +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.numbers.papers.upload( + doc_attachment="example_doc_attachment", + did_ids="did_ids", + doc_id=1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**did_ids:** `str` — Comma-separated record IDs of the phone numbers the document applies to. + +
+
+ +
+
+ +**doc_attachment:** `core.File` — Document file to upload. Allowed formats are PNG, JPG, JPEG, TIFF, BMP, and PDF. Maximum size is 10 MB. + +
+
+ +
+
+ +**doc_id:** `DocumentTypeId` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Profile Config +
client.profile.config.get() -> GetConfigResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the balance and global limits configured for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.profile.config.get() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SmsAndMms SenderIds +
client.sms_and_mms.sender_ids.list() -> SenderIdListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the Sender IDs registered for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.sender_ids.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.sender_ids.create(...) -> SenderIdDetails +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a Sender ID. Use the 10DLC API to create Sender IDs in the US. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.sender_ids.create( + sender_id="Wavix", + type="numeric", + countries=[ + "countries" + ], + usecase="transactional", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sender_id:** `str` — Sender ID name. Can be either an alphanumeric string or a phone number. + +
+
+ +
+
+ +**type:** `SenderIdType` + +
+
+ +
+
+ +**countries:** `typing.List[str]` — Two-letter ISO country codes where the Sender ID is allowlisted. + +
+
+ +
+
+ +**usecase:** `SenderIdCreateRequestUsecase` — Primary use case for the Sender ID. One of `transactional` (account or order notifications), `promo` (marketing and promotional messages), or `authentication` (one-time passcodes and verification codes). + +
+
+ +
+
+ +**monthly_volume:** `typing.Optional[SenderIdCreateRequestMonthlyVolume]` — Expected number of messages sent per month from the Sender ID. One of `1-1000`, `1001-20000`, `20001-50000`, `50001-100000`, or `More than 100000`. Each value is the message-count band for the month. + +
+
+ +
+
+ +**samples:** `typing.Optional[typing.List[str]]` — Message samples. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.sender_ids.get(...) -> SenderIdResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the Sender ID identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.sender_ids.get( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The unique ID of the Sender ID. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.sender_ids.delete(...) -> DeleteSenderIdsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the Sender ID identified by `id`. Deletion is permanent. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.sender_ids.delete( + id="fc34ba88-1eee-476e-b09e-dae63dc441e0", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The unique ID of the Sender ID. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SmsAndMms OptOuts +
client.sms_and_mms.opt_outs.list(...) -> OptOutsListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of phone numbers that have opted out of receiving messages from the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.opt_outs.list( + sender_id="MySender", + campaign_id="C123456", + created_after=datetime.date.fromisoformat("2024-01-01"), + created_before=datetime.date.fromisoformat("2024-12-31"), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sender_id:** `typing.Optional[str]` — Filters opt-outs by the Sender ID they apply to. + +
+
+ +
+
+ +**campaign_id:** `typing.Optional[str]` — Filters opt-outs by the 10DLC campaign ID they apply to. + +
+
+ +
+
+ +**created_after:** `typing.Optional[datetime.date]` — Returns opt-outs created on or after this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**created_before:** `typing.Optional[datetime.date]` — Returns opt-outs created on or before this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Minimum `1`, default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`, maximum `100`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.opt_outs.create(...) -> CreateOptOutsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Opts a phone number out of receiving messages from a Sender ID, a 10DLC campaign, or all outbound messages. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix, OptOut +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.opt_outs.create( + opt_out=OptOut( + number="16419252149", + sender_id="15072429497", + ), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**opt_out:** `OptOut` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SmsAndMms Messages +
client.sms_and_mms.messages.list(...) -> ListMessagesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of SMS and MMS messages for the authenticated account, filtered by direction, date, and other criteria. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.messages.list( + sent_after="2023-04-10", + sent_before="2023-04-13", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", + page=2, + per_page=50, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**type:** `str` — Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + +
+
+ +
+
+ +**sent_after:** `typing.Optional[str]` — Returns messages sent on or after this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**sent_before:** `typing.Optional[str]` — Returns messages sent on or before this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**from:** `typing.Optional[str]` — Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + +
+
+ +
+
+ +**to:** `typing.Optional[str]` — Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, an SMS-enabled number on the Wavix platform. + +
+
+ +
+
+ +**status:** `typing.Optional[MessageDeliveryStatus]` — Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + +
+
+ +
+
+ +**tag:** `typing.Optional[str]` — Filters messages by `tag`. Supported for outbound messages only. + +
+
+ +
+
+ +**message_type:** `typing.Optional[ListMessagesRequestMessageType]` — Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.messages.send(...) -> SendMessagesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Sends an SMS or MMS message. MMS is supported for U.S. numbers only. Track delivery using the returned `message_id` and the message status callback. +**Rate limit**: 20 messages per phone number in 24 hours. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix, MessageBody +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.messages.send( + from_="Wavix", + to="+447537151866", + message_body=MessageBody( + text="Hi there, this is a message from Wavix", + media=None, + ), + callback_url="https://you-site.com/webhook", + validity=3600, + tag="Fall sale", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**from:** `str` — Sender ID. Numeric or alphanumeric. + +
+
+ +
+
+ +**to:** `str` — Recipient phone number. + +
+
+ +
+
+ +**message_body:** `MessageBody` + +
+
+ +
+
+ +**callback_url:** `typing.Optional[str]` — Callback URL for delivery reports. + +
+
+ +
+
+ +**validity:** `typing.Optional[int]` — Message validity period in seconds. Delivery attempts stop after this period expires. + +
+
+ +
+
+ +**tag:** `typing.Optional[str]` — Tag to group messages, such as for a specific campaign. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.messages.get(...) -> GetMessagesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the SMS or MMS message identified by `id`, including its delivery status and content. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.messages.get( + id="3a525ca2-6909-4c72-9399-905adf7f3a74", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The unique ID of the message. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.sms_and_mms.messages.list_all(...) -> str +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Streams matching SMS and MMS messages as newline-delimited JSON (NDJSON), one message per line, for bulk export. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sms_and_mms.messages.list_all( + sent_after="2023-04-10T00:00:00", + sent_before="2023-04-13T23:59:59", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**type:** `str` — Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + +
+
+ +
+
+ +**sent_after:** `typing.Optional[str]` — Returns messages sent on or after this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + +
+
+ +
+
+ +**sent_before:** `typing.Optional[str]` — Returns messages sent on or before this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + +
+
+ +
+
+ +**from:** `typing.Optional[str]` — Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + +
+
+ +
+
+ +**to:** `typing.Optional[str]` — Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, the SMS-enabled number that received the message. + +
+
+ +
+
+ +**status:** `typing.Optional[MessageDeliveryStatus]` — Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + +
+
+ +
+
+ +**tag:** `typing.Optional[str]` — Filters messages by `tag`. Supported for outbound messages only. + +
+
+ +
+
+ +**message_type:** `typing.Optional[ListAllMessagesRequestMessageType]` — Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SpeechAnalytics File +
client.speech_analytics.file.get(...) -> typing.Iterator[bytes] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the original audio file submitted for the transcription identified by `request_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.speech_analytics.file.get( + request_id="request_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_id:** `str` — The `request_id` of the transcription, returned when the file was uploaded. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## SubAccounts Transactions +
client.sub_accounts.transactions.list(...) -> SubAccountsTransactionsListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of billing transactions for the sub-account identified by `id`, within the requested date range. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.sub_accounts.transactions.list( + id=123, + from_date=datetime.date.fromisoformat("2023-01-01"), + to_date=datetime.date.fromisoformat("2023-12-31"), + type=[ + 1 + ], + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `int` — The unique ID of the sub-account. + +
+
+ +
+
+ +**from_date:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to_date:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**type:** `typing.Optional[typing.Union[int, typing.Sequence[int]]]` — Filters transactions by type. Accepts a single transaction type code or an array of codes. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc Brands +
client.ten_dlc.brands.list(...) -> ListBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of 10DLC Brands for the authenticated account, filtered by date, name, legal name, and status. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.list( + dba_name="Brand", + company_name="Company", + entity_type="PRIVATE_PROFIT", + status="VERIFIED", + country="US", + show_deleted=False, + ein_taxid="999999999", + mock=False, + created_before="2024-08-22", + created_after="2024-08-22", + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**dba_name:** `typing.Optional[str]` — Filters Brands by `dba_name` (doing-business-as name). Matches partial values. + +
+
+ +
+
+ +**company_name:** `typing.Optional[str]` — Filters Brands by `company_name` (registered legal name). Matches partial values. + +
+
+ +
+
+ +**entity_type:** `typing.Optional[str]` — Filters Brands by business entity type, such as `PRIVATE_PROFIT`. + +
+
+ +
+
+ +**status:** `typing.Optional[str]` — Filters Brands by identity verification status, such as `VERIFIED`. + +
+
+ +
+
+ +**country:** `typing.Optional[str]` — Filters Brands by registration country, as an ISO 3166-1 alpha-2 code (e.g., `US`). + +
+
+ +
+
+ +**show_deleted:** `typing.Optional[bool]` — When `true`, includes deleted Brands in the results. Default `false`. + +
+
+ +
+
+ +**ein_taxid:** `typing.Optional[str]` — Filters Brands by their Employer Identification Number (EIN) or tax ID. + +
+
+ +
+
+ +**mock:** `typing.Optional[bool]` — When `true`, returns only mock Brands used for testing. Default `false`. + +
+
+ +
+
+ +**created_before:** `typing.Optional[str]` — Returns brands created on or before this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**created_after:** `typing.Optional[str]` — Returns brands created on or after this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brands.create(...) -> CreateBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Registers a 10DLC Brand. TCR automatically verifies the brand identity. Only brands with `VERIFIED` or `VETTED_VERIFIED` identity status can register 10DLC Campaigns. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix, TenDlcBrandCreateRequestZero +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.create( + request=TenDlcBrandCreateRequestZero(), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `TenDlcBrandCreateRequest` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brands.get(...) -> GetBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.get( + brand_id="BM20QP9", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brands.update(...) -> UpdateBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the 10DLC Brand identified by `brand_id`. Changing identity fields, including `ein_taxid`, `ein_taxid_country`, and `entity_type`, resets the Brand status to `UNVERIFIED` and triggers automatic re-submission. Brands in `VETTED_VERIFIED` status or with active Campaigns cannot be updated. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.update( + brand_id="BM20QP9", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**dba_name:** `typing.Optional[str]` — Brand name or DBA + +
+
+ +
+
+ +**company_name:** `typing.Optional[str]` — Legal name of the company + +
+
+ +
+
+ +**entity_type:** `typing.Optional[TenDlcBrandUpdateRequestEntityType]` — Legal entity type of the company. One of `PRIVATE_PROFIT` (privately held for-profit company), `PUBLIC_PROFIT` (publicly traded for-profit company), `NON_PROFIT` (non-profit organization), or `GOVERNMENT` (government entity). + +
+
+ +
+
+ +**vertical:** `typing.Optional[TenDlcBrandUpdateRequestVertical]` + +Business segment the Brand operates in. One of: + +- `HEALTHCARE` — healthcare. +- `PROFESSIONAL` — professional services. +- `RETAIL` — retail. +- `TECHNOLOGY` — technology. +- `EDUCATION` — education. +- `FINANCIAL` — financial services. +- `NON_PROFIT` — non-profit organizations. +- `GOVERNMENT` — government entities. +- `OTHER` — any segment not listed above. + +
+
+ +
+
+ +**ein_taxid:** `typing.Optional[str]` — IRS Employee Identification Number (EIN) for US-based or foreign companies with EIN. The numeric portion of Tax ID for companies incorporated in other countries. + +
+
+ +
+
+ +**ein_taxid_country:** `typing.Optional[str]` — 2-letter ISO country code of the Tax ID issuing country + +
+
+ +
+
+ +**website:** `typing.Optional[str]` — The website of the business + +
+
+ +
+
+ +**stock_symbol:** `typing.Optional[str]` — The stock symbol of the Brand. For PUBLIC_PROFIT Brands only. + +
+
+ +
+
+ +**stock_exchange:** `typing.Optional[str]` — The stock exchange code. For PUBLIC_PROFIT Brands only. + +
+
+ +
+
+ +**first_name:** `typing.Optional[str]` — The first name of the business contact + +
+
+ +
+
+ +**last_name:** `typing.Optional[str]` — The last name of the business contact + +
+
+ +
+
+ +**phone_number:** `typing.Optional[str]` — The support contact telephone in E.164 format + +
+
+ +
+
+ +**email:** `typing.Optional[str]` — The email address of the support contact + +
+
+ +
+
+ +**street_address:** `typing.Optional[str]` — Street name and house number + +
+
+ +
+
+ +**city:** `typing.Optional[str]` — The city name + +
+
+ +
+
+ +**state_or_province:** `typing.Optional[str]` — State or province. For the United States, use 2 character codes. + +
+
+ +
+
+ +**zip:** `typing.Optional[str]` — The business zip or postal code + +
+
+ +
+
+ +**country:** `typing.Optional[str]` — 2-letter ISO country code the business address + +
+
+ +
+
+ +**mock:** `typing.Optional[bool]` — Mock flag for testing (optional, defaults to false) + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brands.delete(...) -> DeleteBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes a 10DLC Brand. Brands with active campaigns cannot be deleted. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.delete( + brand_id="BM20QP9", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brands.qualify_usecase(...) -> QualifyUsecaseBrandsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the qualification results for a 10DLC Brand use case. Includes MNO-specific attributes, restrictions, and fees. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brands.qualify_usecase( + brand_id="BMQFB7X", + use_case="AGENTS_FRANCHISES", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**use_case:** `QualifyUsecaseBrandsRequestUseCase` — Name of the use case to qualify the Brand for. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc BrandAppeals +
client.ten_dlc.brand_appeals.list(...) -> typing.List[TenDlcBrandAppeal] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the identity verification appeals submitted for the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_appeals.list( + brand_id="BM20QP9", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_appeals.create(...) -> CreateBrandAppealsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Submits an appeal for 10DLC brand identity verification. Provide any additional documentation to support the appeal. Use `appeal_category` to specify the appeal type: +- `VERIFY_TAX_ID` — Use if the brand is UNVERIFIED due to a tax ID mismatch. Applies to private companies, public companies, non-profits, and government entities. +- `VERIFY_NON_PROFIT` — Use if a non-profit brand is UNVERIFIED or VERIFIED but missing tax-exempt status. +- `VERIFY_GOVERNMENT` — Use if a government brand is UNVERIFIED or VERIFIED but missing government entity status. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_appeals.create( + brand_id="BM20QP9", + appeal_categories=[ + "VERIFY_TAX_ID" + ], + evidence=[ + "855dff49-c097-4645-3983-08dcb9856232" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**appeal_categories:** `typing.List[str]` — List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + +
+
+ +
+
+ +**evidence:** `typing.List[str]` — List of evidence IDs associated with the appeal. + +
+
+ +
+
+ +**explanation:** `typing.Optional[str]` — Appeal comment or justification. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc BrandEvidence +
client.ten_dlc.brand_evidence.list(...) -> ListBrandEvidenceResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the evidence files uploaded for the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_evidence.list( + brand_id="B6AI7PA", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_evidence.upload(...) -> UploadBrandEvidenceResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Uploads a supporting evidence file for the 10DLC Brand identified by `brand_id`. Supported formats include `.jpg`, `.png`, and `.pdf`. Maximum size is 10 MB. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_evidence.upload( + brand_id="B6AI7PA", + file="example_file", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**file:** `core.File` — Evidence file to upload. Maximum size is 10 MB. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_evidence.get(...) -> typing.Iterator[bytes] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the Brand evidence file identified by the evidence ID. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_evidence.get( + brand_id="brand_id", + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**id:** `str` — The unique ID of the Brand evidence file. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_evidence.delete(...) -> DeleteBrandEvidenceResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the Brand evidence file identified by the evidence ID. Deletion is permanent. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_evidence.delete( + brand_id="B6AI7PA", + id="191eb205-8357-4d71-b8da-160a25a000d7", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**id:** `str` — The unique ID of the Brand evidence file. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc BrandVettings +
client.ten_dlc.brand_vettings.list(...) -> typing.List[TenDlcBrandVetting] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the external vettings for the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_vettings.list( + brand_id="B6AI7PA", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_vettings.create(...) -> TenDlcBrandVetting +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Requests external vetting for a 10DLC Brand. Supported providers: `AEGIS`, `CV`, `WMC`. Supported classes: `STANDARD`, `ENHANCED`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_vettings.create( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_class="STANDARD", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**evp_id:** `str` — Code identifying the external vetting provider to perform the vetting. + +
+
+ +
+
+ +**vetting_class:** `str` — Class of vetting to request, such as `STANDARD` or `ENHANCED`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_vettings.import(...) -> TenDlcBrandVetting +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Imports an existing external vetting record into the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_vettings.import_( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_id="13d8e00c-3cb4-4dc0-9e26-d5057fa938d9", + vetting_token="3oDcE1vq8OR43claMa6Thu/7V4vzZywAfKRgiJnXDjlw+08wpWbGqOssAXKgeZibHCLaGgXvU/yPb7kISeeb5qGdisGRLdhPnSNpvRR82RnCWYNpTp92orlJWjTJU8ZGmNxL5MwK0tt/9SxCha36iTtPV2+4vND8xCPe5suItuQTonG4A3Yi6F1LMqihgwdesRjxJnKqcE7Thcv9ug1NyNPYEZQvPugFj2F2DdU6jFZcOWgXsnE7ucZ+xNaNX9LkF9if3v0hrcviG9L8bUUrpPBGr02txP0i+cPBTLbj4Rq1Ox83R+WUx1gnoXHCIU1ByDGWvQq2Ef4qxGVOwPJHJbja1BovxKBk4YJxiz8OSO68QAIEfxuPTpj5eZz7KEFtFmBIVaVmxBDe4b8Tpl01C2rek7xgPzXaoURvh7CQVnVmJL00DTWKvyOmUOQQW901XEcgcJ7VWgfIvxhIMuXEXXtVDGNowmEc9JQXXYHVlGuN5QicSbApkwwqRZI7TQ4lsS66zCfqomIIJyBNRJpl+8sGwsa2J2h6fEkAD77J9zdUgIKXMFamHbvRadCKMZNIbMrkOC7PuOjZdSiWKh5A8FSjzkv3PlN2hRDqkaODEoodp5pTQeBtNe37+uAMOuHNfsZXlwvfMgCZjiZJ9HQNSLhJBUq7/IvT/EzszUk4HPTj/WFSbT1YrrkDi+zrB20ZDY9lZFWxN1hlYQoNcanDAAWPmw/yW1+8DroL5WIMGsXX3WFGOG7eWB1GHgFQsziAeRQl78u1qOvsRMN08+GrkASBJwqwy5l7xCesUKqbz3O0QA/dwzzsWIDvFPavZpjqMBSjRTurQLFahAaGmdY0BX/Ii+s2+OxfaHQIa1lgucm0P7GPKeZvLX/8boO01Onr/87ra+NX7ABvQb+SXvwsg+Bm5CziWB6DMKDKRD/KQjHxpjIY35UwSEW7G4ixux7ufizXttthHfPJWd/rWFhfYigFhVLgIPCR12smwFVuZwM7ujvY2CIM0X4E0dsX9uVHkgYmqRIdNf5vshpmRuIcHsXZpTJP/tD7zQM6m214c5xkJSfAVIaD7WzRYS4eVL+R3z4u+6n5p6FjuWSjSzuEffUai3HCWjes4JbtDSjIwoG0tOMtBukgPbreH+pjXcvnhU+1QhCV2aIdG6C3FmaI5Uoo/mthJyiFAThwtOpxQ5YkdsRunqVVEFYZfMNEn4Ig2clCFrLOm46JB2wPcLGP2MoH5RqajYzQ6IV8IXIFQVzG0C7HoHsBkVp+GrpnH6N0FCKR+fpbGjigM2lLf4pYBhChUY4ao9hvV1hd8ikS6QoasvDLPytBBa1YAwbSa8d7YdwO6fXfQqetfS8S9gbHD0zxazw5p9Lp5fXFmajDNkD2voYNMzOHJMMHG/49pWV2", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**evp_id:** `str` — Code identifying the external vetting provider that issued the vetting. + +
+
+ +
+
+ +**vetting_id:** `str` — Unique identifier of the vetting request to import. + +
+
+ +
+
+ +**vetting_token:** `str` — Token issued by the vetting provider that uniquely identifies the vetting result to import. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc BrandVettingAppeals +
client.ten_dlc.brand_vetting_appeals.list(...) -> typing.List[TenDlcBrandVettingAppeal] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the external vetting appeals for the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_vetting_appeals.list( + brand_id="BMQFB7X", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.brand_vetting_appeals.create(...) -> CreateBrandVettingAppealsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Submits an appeal for an external vetting of the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.brand_vetting_appeals.create( + brand_id="B6AI7PA", + appeal_categories=[ + "VERIFY_TAX_ID" + ], + evidence=[ + "855dff49-c097-4645-3983-08dcb9856232" + ], +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**appeal_categories:** `typing.List[str]` — List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT`, `LOW_SCORE`. + +
+
+ +
+
+ +**evidence:** `typing.List[str]` — List of evidence IDs associated with the appeal. + +
+
+ +
+
+ +**explanation:** `typing.Optional[str]` — Appeal comment or justification. + +
+
+ +
+
+ +**evp_id:** `typing.Optional[str]` — EVP ID. + +
+
+ +
+
+ +**vetting_id:** `typing.Optional[str]` — Vetting ID. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc Campaigns +
client.ten_dlc.campaigns.list(...) -> ListCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of 10DLC Campaigns for the authenticated account, filtered by date, status, and use case. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.list( + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat("2024-08-22"), + created_after=datetime.date.fromisoformat("2024-08-22"), + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**name:** `typing.Optional[str]` — Filters Campaigns by name. Matches partial values. + +
+
+ +
+
+ +**usecase:** `typing.Optional[str]` — Filters Campaigns by use case. + +
+
+ +
+
+ +**status:** `typing.Optional[str]` — Filters Campaigns by status. + +
+
+ +
+
+ +**mock:** `typing.Optional[bool]` — When `true`, returns only mock Campaigns used for testing. Default `false`. + +
+
+ +
+
+ +**created_before:** `typing.Optional[datetime.date]` — Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**created_after:** `typing.Optional[datetime.date]` — Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.list_by_brand(...) -> ListByBrandCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of 10DLC Campaigns associated with the 10DLC Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.list_by_brand( + brand_id="BM20QP9", + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat("2024-08-22"), + created_after=datetime.date.fromisoformat("2024-08-22"), + page=1, + per_page=25, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**name:** `typing.Optional[str]` — Filters Campaigns by name. Matches partial values. + +
+
+ +
+
+ +**usecase:** `typing.Optional[str]` — Filters Campaigns by use case. + +
+
+ +
+
+ +**status:** `typing.Optional[str]` — Filters Campaigns by status. + +
+
+ +
+
+ +**mock:** `typing.Optional[bool]` — When `true`, returns only mock Campaigns used for testing. Default `false`. + +
+
+ +
+
+ +**created_before:** `typing.Optional[datetime.date]` — Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**created_after:** `typing.Optional[datetime.date]` — Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + +
+
+ +
+
+ +**page:** `typing.Optional[int]` — Page number to retrieve. Default `1`. + +
+
+ +
+
+ +**per_page:** `typing.Optional[int]` — Number of records to return per page. Default `25`. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.create(...) -> CreateCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Registers a 10DLC Campaign under the 10DLC Brand identified by `brand_id`. The Brand must have a verified identity status. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.create( + brand_id="BM20QP9", + affiliate_marketing=False, + age_gated=False, + auto_renewal=False, + direct_lending=False, + embedded_links=False, + embedded_phones=False, + embedded_link_sample="https://site.com/verify", + description="Our campaign aims to …", + optin_workflow="Our SMS ...", + help=True, + help_keywords="help", + help_message="For help, please visit www.site.com. To opt-out, reply STOP.", + optin=True, + optin_keywords="begin,start", + optin_message="You are now opted-in for help please reply HELP, to stop please reply STOP", + optout=True, + optout_keywords="stop,quit,unsubscribe", + optout_message="You are now opted out and will receive no further messages", + name="My first campaign", + sample1="Your verification code is XXXXXX", + sample2="XXXX is your verification code", + sample3="Your code is XXXXXX, valid for 10 minutes", + sample4="Use code XXXXXX to confirm your login", + sample5="XXXXXX is your one-time passcode", + mock=False, + usecase="2FA", + privacy_policy="https://site.com/privacy-policy", + terms_conditions="https://site.com/terms-and-conditions", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**affiliate_marketing:** `bool` — Indicates whether the Campaign is used for affiliate marketing. + +
+
+ +
+
+ +**age_gated:** `bool` — Indicates whether the Campaign messages contain age-gated content. + +
+
+ +
+
+ +**auto_renewal:** `bool` — Indicates whether the Campaign is automatically renewed at the end of each billing period. + +
+
+ +
+
+ +**direct_lending:** `bool` — Indicates whether the Campaign messages contain direct lending content. + +
+
+ +
+
+ +**embedded_links:** `bool` — Indicates whether the Campaign messages contain embedded links. + +
+
+ +
+
+ +**description:** `str` — Description of the Campaign and its messaging purpose. + +
+
+ +
+
+ +**optin_workflow:** `str` — Description of the workflow through which subscribers opt in to the Campaign. + +
+
+ +
+
+ +**help:** `bool` — Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword such as HELP or INFO. + +
+
+ +
+
+ +**help_keywords:** `str` — Comma-separated list of help keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**help_message:** `str` — Acknowledgement sent when a subscriber texts a help keyword. + +
+
+ +
+
+ +**optin:** `bool` — Indicates whether the Campaign requires subscribers to opt in before receiving messages. + +
+
+ +
+
+ +**optin_keywords:** `str` — Comma-separated list of opt-in keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**optin_message:** `str` — Acknowledgement sent when a subscriber texts an opt-in keyword. + +
+
+ +
+
+ +**optout:** `bool` — Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword such as STOP or QUIT. + +
+
+ +
+
+ +**optout_keywords:** `str` — Comma-separated list of opt-out keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**optout_message:** `str` — Acknowledgement sent when a subscriber texts an opt-out keyword. + +
+
+ +
+
+ +**name:** `str` — Display name of the Campaign. + +
+
+ +
+
+ +**sample1:** `str` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**mock:** `bool` — Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + +
+
+ +
+
+ +**usecase:** `str` — Registered use case for the Campaign, such as `2FA` or `MARKETING`. + +
+
+ +
+
+ +**terms_conditions:** `str` — URL of the Campaign terms and conditions. + +
+
+ +
+
+ +**embedded_phones:** `typing.Optional[bool]` — Indicates whether the Campaign messages contain embedded phone numbers. + +
+
+ +
+
+ +**embedded_link_sample:** `typing.Optional[str]` — Sample of an embedded link used in Campaign messages. + +
+
+ +
+
+ +**sample2:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample3:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample4:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample5:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**privacy_policy:** `typing.Optional[str]` — URL of the Campaign privacy policy. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.get(...) -> GetCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the 10DLC Campaign identified by `campaign_id` under the Brand identified by `brand_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.get( + brand_id="BM20QP9", + campaign_id="CKLCK95", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.update(...) -> UpdateCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the 10DLC Campaign identified by `campaign_id`. Only the provided fields are changed. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.update( + brand_id="BM20QP9", + campaign_id="CKLCK95", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**name:** `typing.Optional[str]` — Display name of the Campaign. + +
+
+ +
+
+ +**usecase:** `typing.Optional[TenDlcCampaignUpdateRequestUsecase]` — Registered use case for the Campaign. One of `CUSTOMER_CARE` (customer support messaging), `MARKETING` (promotional content), `ACCOUNT_NOTIFICATION` (account-related alerts), `FRAUD_ALERT` (fraud and suspicious-activity warnings), `PUBLIC_SERVICE_ANNOUNCEMENT` (public-interest notices), or `SECURITY_ALERT` (security-related warnings). + +
+
+ +
+
+ +**description:** `typing.Optional[str]` — Description of the Campaign and its messaging purpose. + +
+
+ +
+
+ +**embedded_links:** `typing.Optional[bool]` — Indicates whether the Campaign messages contain embedded links. + +
+
+ +
+
+ +**embedded_phones:** `typing.Optional[bool]` — Indicates whether the Campaign messages contain embedded phone numbers. + +
+
+ +
+
+ +**age_gated:** `typing.Optional[bool]` — Indicates whether the Campaign messages contain age-gated content. + +
+
+ +
+
+ +**direct_lending:** `typing.Optional[bool]` — Indicates whether the Campaign messages contain direct lending content. + +
+
+ +
+
+ +**optin:** `typing.Optional[bool]` — Indicates whether the Campaign requires subscribers to opt in before receiving messages. + +
+
+ +
+
+ +**optout:** `typing.Optional[bool]` — Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword. + +
+
+ +
+
+ +**help:** `typing.Optional[bool]` — Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword. + +
+
+ +
+
+ +**sample1:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample2:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample3:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample4:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**sample5:** `typing.Optional[str]` — Sample message demonstrating the content sent through the Campaign. + +
+
+ +
+
+ +**optin_workflow:** `typing.Optional[str]` — Description of the workflow through which subscribers opt in to the Campaign. + +
+
+ +
+
+ +**help_message:** `typing.Optional[str]` — Acknowledgement sent when a subscriber texts a help keyword. + +
+
+ +
+
+ +**optin_message:** `typing.Optional[str]` — Acknowledgement sent when a subscriber texts an opt-in keyword. + +
+
+ +
+
+ +**optout_message:** `typing.Optional[str]` — Acknowledgement sent when a subscriber texts an opt-out keyword. + +
+
+ +
+
+ +**auto_renewal:** `typing.Optional[bool]` — Indicates whether the Campaign is automatically renewed at the end of each billing period. + +
+
+ +
+
+ +**optin_keywords:** `typing.Optional[str]` — Comma-separated list of opt-in keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**help_keywords:** `typing.Optional[str]` — Comma-separated list of help keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**optout_keywords:** `typing.Optional[str]` — Comma-separated list of opt-out keywords. Keywords are case-insensitive. + +
+
+ +
+
+ +**terms_conditions:** `typing.Optional[str]` — URL of the Campaign terms and conditions. + +
+
+ +
+
+ +**privacy_policy:** `typing.Optional[str]` — URL of the Campaign privacy policy. + +
+
+ +
+
+ +**embedded_link_sample:** `typing.Optional[str]` — Sample of an embedded link used in Campaign messages. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.delete(...) -> DeleteCampaignsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes a 10DLC Campaign. Associated phone numbers cannot be used as Sender IDs once the Campaign is deleted. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.delete( + brand_id="BM20QP9", + campaign_id="CKLCK95", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaigns.nudge(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Requests action on a pending or rejected 10DLC Campaign. Use `nudge_intent` to specify the action: +- `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. +Note: +- The Campaign must be at least 72 hours old. +- Only one nudge request per Campaign is allowed every 24 hours. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaigns.nudge( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + nudge_intent="REVIEW", + description="Please review the campaign.", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**nudge_intent:** `str` + +Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. +Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + +
+
+ +
+
+ +**description:** `str` — Description of the nudge request. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc Subscriptions +
client.ten_dlc.subscriptions.list() -> typing.List[TenDlcEventSubscription] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the 10DLC event subscriptions for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.subscriptions.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.subscriptions.create(...) -> CreateSubscriptionsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Registers a callback URL to receive Wavix 10DLC event notifications. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.subscriptions.create( + subscription_category="brand", + url="https://webhook.url", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `TenDlcEventSubscription` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.subscriptions.delete(...) -> DeleteSubscriptionsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Removes the 10DLC event subscription for the specified event category. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.subscriptions.delete( + subscription_category="number", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**subscription_category:** `str` — Event category to unsubscribe from. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TenDlc CampaignNumbers +
client.ten_dlc.campaign_numbers.link(...) -> LinkCampaignNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Links a phone number to a 10DLC Campaign. Wavix automatically creates a Sender ID once the number is approved. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaign_numbers.link( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**number:** `str` — The phone number to link to the Campaign, in E.164 format. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaign_numbers.unlink(...) -> UnlinkCampaignNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Unlinks a phone number from a 10DLC Campaign. The associated Sender ID is also deleted. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaign_numbers.unlink( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**number:** `str` — The phone number to unlink from the Campaign, in E.164 format. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.ten_dlc.campaign_numbers.list(...) -> ListCampaignNumbersResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the phone numbers linked to the 10DLC Campaign identified by `campaign_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.ten_dlc.campaign_numbers.list( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**brand_id:** `str` — The unique ID of the 10DLC Brand. + +
+
+ +
+
+ +**campaign_id:** `str` — The unique ID of the 10DLC Campaign. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TwoFa Verification +
client.two_fa.verification.create(...) -> CreateVerificationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a 2FA verification and sends a one-time password (OTP) to the destination phone number over the selected channel. Requires a 2FA service configured in the Wavix portal; the service is reused to generate and validate OTPs. + +The verification proceeds through three steps: +1. Create a verification to generate and send an OTP. +2. Resend the OTP on the same verification if needed. +3. Validate the OTP through the check endpoint. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.verification.create( + service_id="7204a030201211ee9fb47d093f2f127c", + to="447919433768", + channel="sms", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**service_id:** `str` — Unique Wavix 2FA Service ID. Available on the Wavix portal. + +
+
+ +
+
+ +**to:** `str` — End user's phone number to which the verification code will be sent. The phone number must be in E.164 format. + +
+
+ +
+
+ +**channel:** `str` — Channel used to deliver the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.two_fa.verification.resend(...) -> ResendVerificationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Resends the OTP for the verification identified by `session_id` over the specified channel. Previously sent codes are invalidated. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.verification.resend( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + channel="sms", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**session_id:** `str` — The unique ID of the 2FA verification session. + +
+
+ +
+
+ +**channel:** `TwoFactorVerificationResendRequestChannel` — Channel used to resend the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.two_fa.verification.check(...) -> CheckVerificationResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Validates the OTP submitted by the end user against the verification identified by `session_id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.verification.check( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + code="123456", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**session_id:** `str` — The unique ID of the 2FA verification session. + +
+
+ +
+
+ +**code:** `str` — The code entered by an end user + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.two_fa.verification.cancel(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Cancels the 2FA verification identified by `session_id`. No further codes are sent, and previously sent codes can no longer be validated. A new verification is required to send another code. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.verification.cancel( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**session_id:** `str` — The unique ID of the 2FA verification session. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TwoFa Sessions +
client.two_fa.sessions.list(...) -> typing.List[ListSessionsResponseItem] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the 2FA verifications for the service identified by `service_id`, within the requested date range. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment +import datetime + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.sessions.list( + service_id="7204a030201211ee9fb47d093f2f127c", + from_=datetime.date.fromisoformat("2022-01-01"), + to=datetime.date.fromisoformat("2022-01-31"), +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**service_id:** `str` — The unique ID of the 2FA service. + +
+
+ +
+
+ +**from:** `datetime.date` — Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**to:** `datetime.date` — End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## TwoFa Events +
client.two_fa.events.list(...) -> typing.List[TwoFactorVerificationEvent] +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the lifecycle events of the 2FA verification identified by `session_id`, such as number lookup and code delivery. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.two_fa.events.list( + session_id="8753d4308f2e11ecb75fcdafd6d2d690", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**session_id:** `str` — The unique ID of the 2FA verification session. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +## Webrtc Tokens +
client.webrtc.tokens.list() -> WebRtcTokensListResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a paginated list of active Wavix Embeddable widget tokens for the authenticated account. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.webrtc.tokens.list() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.webrtc.tokens.create(...) -> WebRtcTokenResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Creates a Wavix Embeddable widget token that authenticates a browser-based softphone session. The token expires after `ttl` seconds. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.webrtc.tokens.create( + sip_trunk="my-sip-trunk", + payload={ + "user_id": "42" + }, + ttl=3600, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**sip_trunk:** `str` — Name of the SIP trunk the token authenticates against. + +
+
+ +
+
+ +**payload:** `typing.Optional[typing.Dict[str, typing.Any]]` — Arbitrary client-defined data to associate with the token. + +
+
+ +
+
+ +**ttl:** `typing.Optional[int]` — Time to live in seconds. Pass `null` for no expiration. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.webrtc.tokens.get(...) -> WebRtcToken +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the Wavix Embeddable widget token identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.webrtc.tokens.get( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The UUID of the widget token to retrieve. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.webrtc.tokens.update(...) -> WebRtcToken +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Updates the `payload` carried by the Wavix Embeddable widget token identified by `id`. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.webrtc.tokens.update( + id="id", + payload={ + "key": "value" + }, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The UUID of the widget token to update. + +
+
+ +
+
+ +**payload:** `typing.Dict[str, typing.Any]` — Arbitrary client-defined data to associate with the token, replacing the existing payload. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.webrtc.tokens.delete(...) -> SuccessResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Deletes the Wavix Embeddable widget token identified by `id`. The token can no longer authenticate widget sessions, and any active session using it ends. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from wavix import Wavix +from wavix.environment import WavixEnvironment + +client = Wavix( + token="", + environment=WavixEnvironment.DEFAULT, +) + +client.webrtc.tokens.delete( + id="id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `str` — The UUID of the widget token to delete. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..443b1af --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +httpx>=0.21.2 +pydantic>= 1.9.2 +pydantic-core>=2.18.2,<3.0.0 +typing_extensions>= 4.0.0 diff --git a/src/wavix/__init__.py b/src/wavix/__init__.py new file mode 100644 index 0000000..35147bb --- /dev/null +++ b/src/wavix/__init__.py @@ -0,0 +1,801 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + AccountErrorResponse, + AccountLimits, + AllowedIPs, + AllowedIPsItem, + ApiKey, + ApiKeyCallsScopePermission, + ApiKeyCallsScopePermissionAllow, + ApiKeyScopePermission, + ApiKeyScopePermissionAllow, + AvailableNumber, + AvailableNumberListResponse, + BadRequestErrorBody, + BillingTransactionListResponse, + BrandStatusUpdatedWebhook, + BrandStatusUpdatedWebhookStatus, + Call, + CallControlValidationErrorResponse, + CallCreateResponse, + CallCreateResponseEventType, + CallDirection, + CallDisposition, + CallEventType, + CallListResponse, + CallRecordingListResponse, + CallResponse, + CallStatusWebhook, + CallStatusWebhookDirection, + CallStreamChannel, + CallStreamResponse, + CallStreamType, + CallWebhook, + CallWebhookEventType, + CallWebhookListResponse, + CallWebhookListResponseItem, + CallWebhookListResponseItemEventType, + CampaignStatusUpdatedWebhook, + CampaignStatusUpdatedWebhookStatus, + CartResponse, + Cdr, + CdrListResponse, + CdrResponse, + CdrTranscriptionCompletedWebhook, + CdrTranscriptionCompletedWebhookStatus, + CdrTranscriptionResponse, + CdrTranscriptionSearchResponse, + CdrWithTranscription, + City, + CityListResponse, + Country, + CountryHasNoRegionsErrorResponse, + CountryListResponse, + DocumentType, + DocumentTypeId, + FileTranscriptResponse, + FileTranscriptTurn, + FileTranscriptionCompletedWebhook, + FileTranscriptionResponse, + FileTranscriptionResponseLanguage, + FileTranscriptionResponseStatus, + FinancialTransaction, + ForbiddenErrorResponse, + InboundCallDestination, + InboundCallTransport, + InboundMessage, + InvalidRecording, + Invoice, + InvoiceListResponse, + ListBrandEvidenceResponse, + ListSessionsResponseItem, + Message, + MessageBody, + MessageCreateRequest, + MessageDeliveryStatus, + MessageListResponse, + MessageResponse, + MessagesDeliveryReport, + NotFoundErrorBody, + NotFoundErrorResponse, + Number, + NumberDestination, + NumberDocument, + NumberListResponse, + NumberStatusUpdatedWebhook, + NumberStatusUpdatedWebhookStatus, + NumberValidatorCreateBulkResponse, + NumberValidatorCreateBulkResponseRequestId, + OnCallEventPayload, + OnCallEventPayloadType, + OptOut, + OptOutItem, + OptOutsListResponse, + Pagination, + PhoneLookupDetails, + PhoneNumberValidationType, + PhoneValidationBatchResponse, + PhoneValidationBatchResultResponse, + PhoneValidationResponse, + PhoneValidationResultResponse, + ProfileConfigResponse, + ProfileResponse, + ProfileResponseCompanyInfo, + ProfileResponseCompanyInfoCountry, + ProfileResponseCompanyInfoIndustry, + ProfileResponseDefaultDestinationsItem, + RecordNotFoundErrorResponse, + Recording, + RecordingDeletedErrorResponse, + Region, + RegionListResponse, + SendMessagesResponse, + SenderId, + SenderIdDetails, + SenderIdListResponse, + SenderIdResponse, + SenderIdResponseType, + SenderIdType, + ShortLinkMetricsItem, + ShortLinkMetricsResponse, + ShortLinkResponse, + SipTrunkCreateRequest, + SipTrunkCreateRequestAllowedIpsItem, + SipTrunkCreateRequestHostRequest, + SipTrunkListResponse, + SipTrunkResponse, + SipTrunkSummary, + SipTrunkSummaryHostRequest, + SubAccountsListResponse, + SubAccountsTransactionsListResponse, + SubAccountsTransactionsListResponseTransactionsItem, + SubOrganizationResponse, + SubOrganizationResponseDefaultDestinations, + SubOrganizationResponseStatus, + SubmitFileTranscriptionResponse, + SuccessResponse, + TcrErrorMessage, + TcrFeedback, + TcrFeedbackCategory, + TenDlcBrand, + TenDlcBrandAppeal, + TenDlcBrandAppealCreateRequest, + TenDlcBrandAppealOutcome, + TenDlcBrandAppealOutcomeFeedback, + TenDlcBrandAppealOutcomeVettingStatus, + TenDlcBrandCreateRequest, + TenDlcBrandCreateRequestEntityType, + TenDlcBrandCreateRequestStockExchange, + TenDlcBrandCreateRequestStockExchangeEntityType, + TenDlcBrandCreateRequestVertical, + TenDlcBrandCreateRequestZero, + TenDlcBrandCreateRequestZeroEntityType, + TenDlcBrandEntityType, + TenDlcBrandEvidence, + TenDlcBrandIdentityVerificationStatus, + TenDlcBrandListResponse, + TenDlcBrandListResponsePagination, + TenDlcBrandQualificationResult, + TenDlcBrandStatus, + TenDlcBrandVetting, + TenDlcBrandVettingAppeal, + TenDlcBrandVettingAppealAppealOutcome, + TenDlcBrandVettingAppealAppealOutcomeFeedback, + TenDlcBrandVettingAppealOutcome, + TenDlcBrandVettingAppealOutcomeFeedback, + TenDlcBrandVettingAppealOutcomeReason, + TenDlcCampaign, + TenDlcCampaignListResponse, + TenDlcCampaignListResponsePagination, + TenDlcCampaignNudgeRequest, + TenDlcCampaignNumber, + TenDlcCampaignNumberListResponse, + TenDlcEventSubscription, + TenDlcmnoMetadata, + TransactionStatus, + TransactionType, + TranscriptTurn, + TranscriptionFilter, + TranscriptionFilterAgent, + TranscriptionFilterAny, + TranscriptionFilterClient, + TranscriptionLanguage, + TranscriptionReference, + TranscriptionStatus, + TtsLanguage, + TtsVoiceId, + TwoFactorVerificationCheckResponse, + TwoFactorVerificationEvent, + TwoFactorVerificationResendResponse, + TwoFactorVerificationResponse, + UnauthorizedErrorResponse, + UnprocessableEntityErrorBody, + ValidationErrorResponse, + VoiceCampaignCreateRequest, + VoiceCampaignResponse, + VoiceCampaignsCreateResponse, + VoiceCampaignsCreateResponseVoiceCampaign, + VoiceCampaignsGetResponse, + VoiceCampaignsGetResponseVoiceCampaign, + WebRtcToken, + WebRtcTokenResponse, + WebRtcTokensListResponse, + ) + from .errors import ( + BadRequestError, + ForbiddenError, + NotFoundError, + ServiceUnavailableError, + TooManyRequestsError, + UnauthorizedError, + UnprocessableEntityError, + ) + from . import ( + api_keys, + billing, + buy, + call_control, + call_recording, + call_webhooks, + cart, + cdrs, + link_shortener, + number_validator, + numbers, + profile, + sip_trunks, + sms_and_mms, + speech_analytics, + sub_accounts, + ten_dlc, + two_fa, + voice_campaigns, + webrtc, + ) + from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient + from .call_control import CallDtmfCollectRequestPrompt, CallDtmfCollectRequestPromptSay + from .call_webhooks import CallWebhooksCreateRequestEventType, DeleteCallWebhooksRequestEventType + from .cart import CheckoutCartResponse, GetCartResponse, RemoveCartResponse + from .cdrs import CdrSearchRequestDisposition, CdrSearchRequestType + from .client import AsyncWavix, Wavix + from .environment import WavixEnvironment + from .number_validator import GetNumberValidatorResponse + from .numbers import BulkUpdateNumbersResponse, DeleteNumbersResponse + from .profile import ProfileUpdateRequestCompanyInfo, ProfileUpdateRequestCompanyInfoIndustry + from .speech_analytics import ( + CreateSpeechAnalyticsResponse, + GetSpeechAnalyticsResponse, + GetSpeechAnalyticsResponseLanguage, + GetSpeechAnalyticsResponseStatus, + ) + from .sub_accounts import ( + ListSubAccountsRequestStatus, + SubAccountsCreateRequestDefaultDestinations, + SubAccountsUpdateRequestDefaultDestinations, + SubAccountsUpdateRequestStatus, + ) + from .version import __version__ +_dynamic_imports: typing.Dict[str, str] = { + "AccountErrorResponse": ".types", + "AccountLimits": ".types", + "AllowedIPs": ".types", + "AllowedIPsItem": ".types", + "ApiKey": ".types", + "ApiKeyCallsScopePermission": ".types", + "ApiKeyCallsScopePermissionAllow": ".types", + "ApiKeyScopePermission": ".types", + "ApiKeyScopePermissionAllow": ".types", + "AsyncWavix": ".client", + "AvailableNumber": ".types", + "AvailableNumberListResponse": ".types", + "BadRequestError": ".errors", + "BadRequestErrorBody": ".types", + "BillingTransactionListResponse": ".types", + "BrandStatusUpdatedWebhook": ".types", + "BrandStatusUpdatedWebhookStatus": ".types", + "BulkUpdateNumbersResponse": ".numbers", + "Call": ".types", + "CallControlValidationErrorResponse": ".types", + "CallCreateResponse": ".types", + "CallCreateResponseEventType": ".types", + "CallDirection": ".types", + "CallDisposition": ".types", + "CallDtmfCollectRequestPrompt": ".call_control", + "CallDtmfCollectRequestPromptSay": ".call_control", + "CallEventType": ".types", + "CallListResponse": ".types", + "CallRecordingListResponse": ".types", + "CallResponse": ".types", + "CallStatusWebhook": ".types", + "CallStatusWebhookDirection": ".types", + "CallStreamChannel": ".types", + "CallStreamResponse": ".types", + "CallStreamType": ".types", + "CallWebhook": ".types", + "CallWebhookEventType": ".types", + "CallWebhookListResponse": ".types", + "CallWebhookListResponseItem": ".types", + "CallWebhookListResponseItemEventType": ".types", + "CallWebhooksCreateRequestEventType": ".call_webhooks", + "CampaignStatusUpdatedWebhook": ".types", + "CampaignStatusUpdatedWebhookStatus": ".types", + "CartResponse": ".types", + "Cdr": ".types", + "CdrListResponse": ".types", + "CdrResponse": ".types", + "CdrSearchRequestDisposition": ".cdrs", + "CdrSearchRequestType": ".cdrs", + "CdrTranscriptionCompletedWebhook": ".types", + "CdrTranscriptionCompletedWebhookStatus": ".types", + "CdrTranscriptionResponse": ".types", + "CdrTranscriptionSearchResponse": ".types", + "CdrWithTranscription": ".types", + "CheckoutCartResponse": ".cart", + "City": ".types", + "CityListResponse": ".types", + "Country": ".types", + "CountryHasNoRegionsErrorResponse": ".types", + "CountryListResponse": ".types", + "CreateSpeechAnalyticsResponse": ".speech_analytics", + "DefaultAioHttpClient": "._default_clients", + "DefaultAsyncHttpxClient": "._default_clients", + "DeleteCallWebhooksRequestEventType": ".call_webhooks", + "DeleteNumbersResponse": ".numbers", + "DocumentType": ".types", + "DocumentTypeId": ".types", + "FileTranscriptResponse": ".types", + "FileTranscriptTurn": ".types", + "FileTranscriptionCompletedWebhook": ".types", + "FileTranscriptionResponse": ".types", + "FileTranscriptionResponseLanguage": ".types", + "FileTranscriptionResponseStatus": ".types", + "FinancialTransaction": ".types", + "ForbiddenError": ".errors", + "ForbiddenErrorResponse": ".types", + "GetCartResponse": ".cart", + "GetNumberValidatorResponse": ".number_validator", + "GetSpeechAnalyticsResponse": ".speech_analytics", + "GetSpeechAnalyticsResponseLanguage": ".speech_analytics", + "GetSpeechAnalyticsResponseStatus": ".speech_analytics", + "InboundCallDestination": ".types", + "InboundCallTransport": ".types", + "InboundMessage": ".types", + "InvalidRecording": ".types", + "Invoice": ".types", + "InvoiceListResponse": ".types", + "ListBrandEvidenceResponse": ".types", + "ListSessionsResponseItem": ".types", + "ListSubAccountsRequestStatus": ".sub_accounts", + "Message": ".types", + "MessageBody": ".types", + "MessageCreateRequest": ".types", + "MessageDeliveryStatus": ".types", + "MessageListResponse": ".types", + "MessageResponse": ".types", + "MessagesDeliveryReport": ".types", + "NotFoundError": ".errors", + "NotFoundErrorBody": ".types", + "NotFoundErrorResponse": ".types", + "Number": ".types", + "NumberDestination": ".types", + "NumberDocument": ".types", + "NumberListResponse": ".types", + "NumberStatusUpdatedWebhook": ".types", + "NumberStatusUpdatedWebhookStatus": ".types", + "NumberValidatorCreateBulkResponse": ".types", + "NumberValidatorCreateBulkResponseRequestId": ".types", + "OnCallEventPayload": ".types", + "OnCallEventPayloadType": ".types", + "OptOut": ".types", + "OptOutItem": ".types", + "OptOutsListResponse": ".types", + "Pagination": ".types", + "PhoneLookupDetails": ".types", + "PhoneNumberValidationType": ".types", + "PhoneValidationBatchResponse": ".types", + "PhoneValidationBatchResultResponse": ".types", + "PhoneValidationResponse": ".types", + "PhoneValidationResultResponse": ".types", + "ProfileConfigResponse": ".types", + "ProfileResponse": ".types", + "ProfileResponseCompanyInfo": ".types", + "ProfileResponseCompanyInfoCountry": ".types", + "ProfileResponseCompanyInfoIndustry": ".types", + "ProfileResponseDefaultDestinationsItem": ".types", + "ProfileUpdateRequestCompanyInfo": ".profile", + "ProfileUpdateRequestCompanyInfoIndustry": ".profile", + "RecordNotFoundErrorResponse": ".types", + "Recording": ".types", + "RecordingDeletedErrorResponse": ".types", + "Region": ".types", + "RegionListResponse": ".types", + "RemoveCartResponse": ".cart", + "SendMessagesResponse": ".types", + "SenderId": ".types", + "SenderIdDetails": ".types", + "SenderIdListResponse": ".types", + "SenderIdResponse": ".types", + "SenderIdResponseType": ".types", + "SenderIdType": ".types", + "ServiceUnavailableError": ".errors", + "ShortLinkMetricsItem": ".types", + "ShortLinkMetricsResponse": ".types", + "ShortLinkResponse": ".types", + "SipTrunkCreateRequest": ".types", + "SipTrunkCreateRequestAllowedIpsItem": ".types", + "SipTrunkCreateRequestHostRequest": ".types", + "SipTrunkListResponse": ".types", + "SipTrunkResponse": ".types", + "SipTrunkSummary": ".types", + "SipTrunkSummaryHostRequest": ".types", + "SubAccountsCreateRequestDefaultDestinations": ".sub_accounts", + "SubAccountsListResponse": ".types", + "SubAccountsTransactionsListResponse": ".types", + "SubAccountsTransactionsListResponseTransactionsItem": ".types", + "SubAccountsUpdateRequestDefaultDestinations": ".sub_accounts", + "SubAccountsUpdateRequestStatus": ".sub_accounts", + "SubOrganizationResponse": ".types", + "SubOrganizationResponseDefaultDestinations": ".types", + "SubOrganizationResponseStatus": ".types", + "SubmitFileTranscriptionResponse": ".types", + "SuccessResponse": ".types", + "TcrErrorMessage": ".types", + "TcrFeedback": ".types", + "TcrFeedbackCategory": ".types", + "TenDlcBrand": ".types", + "TenDlcBrandAppeal": ".types", + "TenDlcBrandAppealCreateRequest": ".types", + "TenDlcBrandAppealOutcome": ".types", + "TenDlcBrandAppealOutcomeFeedback": ".types", + "TenDlcBrandAppealOutcomeVettingStatus": ".types", + "TenDlcBrandCreateRequest": ".types", + "TenDlcBrandCreateRequestEntityType": ".types", + "TenDlcBrandCreateRequestStockExchange": ".types", + "TenDlcBrandCreateRequestStockExchangeEntityType": ".types", + "TenDlcBrandCreateRequestVertical": ".types", + "TenDlcBrandCreateRequestZero": ".types", + "TenDlcBrandCreateRequestZeroEntityType": ".types", + "TenDlcBrandEntityType": ".types", + "TenDlcBrandEvidence": ".types", + "TenDlcBrandIdentityVerificationStatus": ".types", + "TenDlcBrandListResponse": ".types", + "TenDlcBrandListResponsePagination": ".types", + "TenDlcBrandQualificationResult": ".types", + "TenDlcBrandStatus": ".types", + "TenDlcBrandVetting": ".types", + "TenDlcBrandVettingAppeal": ".types", + "TenDlcBrandVettingAppealAppealOutcome": ".types", + "TenDlcBrandVettingAppealAppealOutcomeFeedback": ".types", + "TenDlcBrandVettingAppealOutcome": ".types", + "TenDlcBrandVettingAppealOutcomeFeedback": ".types", + "TenDlcBrandVettingAppealOutcomeReason": ".types", + "TenDlcCampaign": ".types", + "TenDlcCampaignListResponse": ".types", + "TenDlcCampaignListResponsePagination": ".types", + "TenDlcCampaignNudgeRequest": ".types", + "TenDlcCampaignNumber": ".types", + "TenDlcCampaignNumberListResponse": ".types", + "TenDlcEventSubscription": ".types", + "TenDlcmnoMetadata": ".types", + "TooManyRequestsError": ".errors", + "TransactionStatus": ".types", + "TransactionType": ".types", + "TranscriptTurn": ".types", + "TranscriptionFilter": ".types", + "TranscriptionFilterAgent": ".types", + "TranscriptionFilterAny": ".types", + "TranscriptionFilterClient": ".types", + "TranscriptionLanguage": ".types", + "TranscriptionReference": ".types", + "TranscriptionStatus": ".types", + "TtsLanguage": ".types", + "TtsVoiceId": ".types", + "TwoFactorVerificationCheckResponse": ".types", + "TwoFactorVerificationEvent": ".types", + "TwoFactorVerificationResendResponse": ".types", + "TwoFactorVerificationResponse": ".types", + "UnauthorizedError": ".errors", + "UnauthorizedErrorResponse": ".types", + "UnprocessableEntityError": ".errors", + "UnprocessableEntityErrorBody": ".types", + "ValidationErrorResponse": ".types", + "VoiceCampaignCreateRequest": ".types", + "VoiceCampaignResponse": ".types", + "VoiceCampaignsCreateResponse": ".types", + "VoiceCampaignsCreateResponseVoiceCampaign": ".types", + "VoiceCampaignsGetResponse": ".types", + "VoiceCampaignsGetResponseVoiceCampaign": ".types", + "Wavix": ".client", + "WavixEnvironment": ".environment", + "WebRtcToken": ".types", + "WebRtcTokenResponse": ".types", + "WebRtcTokensListResponse": ".types", + "__version__": ".version", + "api_keys": ".api_keys", + "billing": ".billing", + "buy": ".buy", + "call_control": ".call_control", + "call_recording": ".call_recording", + "call_webhooks": ".call_webhooks", + "cart": ".cart", + "cdrs": ".cdrs", + "link_shortener": ".link_shortener", + "number_validator": ".number_validator", + "numbers": ".numbers", + "profile": ".profile", + "sip_trunks": ".sip_trunks", + "sms_and_mms": ".sms_and_mms", + "speech_analytics": ".speech_analytics", + "sub_accounts": ".sub_accounts", + "ten_dlc": ".ten_dlc", + "two_fa": ".two_fa", + "voice_campaigns": ".voice_campaigns", + "webrtc": ".webrtc", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccountErrorResponse", + "AccountLimits", + "AllowedIPs", + "AllowedIPsItem", + "ApiKey", + "ApiKeyCallsScopePermission", + "ApiKeyCallsScopePermissionAllow", + "ApiKeyScopePermission", + "ApiKeyScopePermissionAllow", + "AsyncWavix", + "AvailableNumber", + "AvailableNumberListResponse", + "BadRequestError", + "BadRequestErrorBody", + "BillingTransactionListResponse", + "BrandStatusUpdatedWebhook", + "BrandStatusUpdatedWebhookStatus", + "BulkUpdateNumbersResponse", + "Call", + "CallControlValidationErrorResponse", + "CallCreateResponse", + "CallCreateResponseEventType", + "CallDirection", + "CallDisposition", + "CallDtmfCollectRequestPrompt", + "CallDtmfCollectRequestPromptSay", + "CallEventType", + "CallListResponse", + "CallRecordingListResponse", + "CallResponse", + "CallStatusWebhook", + "CallStatusWebhookDirection", + "CallStreamChannel", + "CallStreamResponse", + "CallStreamType", + "CallWebhook", + "CallWebhookEventType", + "CallWebhookListResponse", + "CallWebhookListResponseItem", + "CallWebhookListResponseItemEventType", + "CallWebhooksCreateRequestEventType", + "CampaignStatusUpdatedWebhook", + "CampaignStatusUpdatedWebhookStatus", + "CartResponse", + "Cdr", + "CdrListResponse", + "CdrResponse", + "CdrSearchRequestDisposition", + "CdrSearchRequestType", + "CdrTranscriptionCompletedWebhook", + "CdrTranscriptionCompletedWebhookStatus", + "CdrTranscriptionResponse", + "CdrTranscriptionSearchResponse", + "CdrWithTranscription", + "CheckoutCartResponse", + "City", + "CityListResponse", + "Country", + "CountryHasNoRegionsErrorResponse", + "CountryListResponse", + "CreateSpeechAnalyticsResponse", + "DefaultAioHttpClient", + "DefaultAsyncHttpxClient", + "DeleteCallWebhooksRequestEventType", + "DeleteNumbersResponse", + "DocumentType", + "DocumentTypeId", + "FileTranscriptResponse", + "FileTranscriptTurn", + "FileTranscriptionCompletedWebhook", + "FileTranscriptionResponse", + "FileTranscriptionResponseLanguage", + "FileTranscriptionResponseStatus", + "FinancialTransaction", + "ForbiddenError", + "ForbiddenErrorResponse", + "GetCartResponse", + "GetNumberValidatorResponse", + "GetSpeechAnalyticsResponse", + "GetSpeechAnalyticsResponseLanguage", + "GetSpeechAnalyticsResponseStatus", + "InboundCallDestination", + "InboundCallTransport", + "InboundMessage", + "InvalidRecording", + "Invoice", + "InvoiceListResponse", + "ListBrandEvidenceResponse", + "ListSessionsResponseItem", + "ListSubAccountsRequestStatus", + "Message", + "MessageBody", + "MessageCreateRequest", + "MessageDeliveryStatus", + "MessageListResponse", + "MessageResponse", + "MessagesDeliveryReport", + "NotFoundError", + "NotFoundErrorBody", + "NotFoundErrorResponse", + "Number", + "NumberDestination", + "NumberDocument", + "NumberListResponse", + "NumberStatusUpdatedWebhook", + "NumberStatusUpdatedWebhookStatus", + "NumberValidatorCreateBulkResponse", + "NumberValidatorCreateBulkResponseRequestId", + "OnCallEventPayload", + "OnCallEventPayloadType", + "OptOut", + "OptOutItem", + "OptOutsListResponse", + "Pagination", + "PhoneLookupDetails", + "PhoneNumberValidationType", + "PhoneValidationBatchResponse", + "PhoneValidationBatchResultResponse", + "PhoneValidationResponse", + "PhoneValidationResultResponse", + "ProfileConfigResponse", + "ProfileResponse", + "ProfileResponseCompanyInfo", + "ProfileResponseCompanyInfoCountry", + "ProfileResponseCompanyInfoIndustry", + "ProfileResponseDefaultDestinationsItem", + "ProfileUpdateRequestCompanyInfo", + "ProfileUpdateRequestCompanyInfoIndustry", + "RecordNotFoundErrorResponse", + "Recording", + "RecordingDeletedErrorResponse", + "Region", + "RegionListResponse", + "RemoveCartResponse", + "SendMessagesResponse", + "SenderId", + "SenderIdDetails", + "SenderIdListResponse", + "SenderIdResponse", + "SenderIdResponseType", + "SenderIdType", + "ServiceUnavailableError", + "ShortLinkMetricsItem", + "ShortLinkMetricsResponse", + "ShortLinkResponse", + "SipTrunkCreateRequest", + "SipTrunkCreateRequestAllowedIpsItem", + "SipTrunkCreateRequestHostRequest", + "SipTrunkListResponse", + "SipTrunkResponse", + "SipTrunkSummary", + "SipTrunkSummaryHostRequest", + "SubAccountsCreateRequestDefaultDestinations", + "SubAccountsListResponse", + "SubAccountsTransactionsListResponse", + "SubAccountsTransactionsListResponseTransactionsItem", + "SubAccountsUpdateRequestDefaultDestinations", + "SubAccountsUpdateRequestStatus", + "SubOrganizationResponse", + "SubOrganizationResponseDefaultDestinations", + "SubOrganizationResponseStatus", + "SubmitFileTranscriptionResponse", + "SuccessResponse", + "TcrErrorMessage", + "TcrFeedback", + "TcrFeedbackCategory", + "TenDlcBrand", + "TenDlcBrandAppeal", + "TenDlcBrandAppealCreateRequest", + "TenDlcBrandAppealOutcome", + "TenDlcBrandAppealOutcomeFeedback", + "TenDlcBrandAppealOutcomeVettingStatus", + "TenDlcBrandCreateRequest", + "TenDlcBrandCreateRequestEntityType", + "TenDlcBrandCreateRequestStockExchange", + "TenDlcBrandCreateRequestStockExchangeEntityType", + "TenDlcBrandCreateRequestVertical", + "TenDlcBrandCreateRequestZero", + "TenDlcBrandCreateRequestZeroEntityType", + "TenDlcBrandEntityType", + "TenDlcBrandEvidence", + "TenDlcBrandIdentityVerificationStatus", + "TenDlcBrandListResponse", + "TenDlcBrandListResponsePagination", + "TenDlcBrandQualificationResult", + "TenDlcBrandStatus", + "TenDlcBrandVetting", + "TenDlcBrandVettingAppeal", + "TenDlcBrandVettingAppealAppealOutcome", + "TenDlcBrandVettingAppealAppealOutcomeFeedback", + "TenDlcBrandVettingAppealOutcome", + "TenDlcBrandVettingAppealOutcomeFeedback", + "TenDlcBrandVettingAppealOutcomeReason", + "TenDlcCampaign", + "TenDlcCampaignListResponse", + "TenDlcCampaignListResponsePagination", + "TenDlcCampaignNudgeRequest", + "TenDlcCampaignNumber", + "TenDlcCampaignNumberListResponse", + "TenDlcEventSubscription", + "TenDlcmnoMetadata", + "TooManyRequestsError", + "TransactionStatus", + "TransactionType", + "TranscriptTurn", + "TranscriptionFilter", + "TranscriptionFilterAgent", + "TranscriptionFilterAny", + "TranscriptionFilterClient", + "TranscriptionLanguage", + "TranscriptionReference", + "TranscriptionStatus", + "TtsLanguage", + "TtsVoiceId", + "TwoFactorVerificationCheckResponse", + "TwoFactorVerificationEvent", + "TwoFactorVerificationResendResponse", + "TwoFactorVerificationResponse", + "UnauthorizedError", + "UnauthorizedErrorResponse", + "UnprocessableEntityError", + "UnprocessableEntityErrorBody", + "ValidationErrorResponse", + "VoiceCampaignCreateRequest", + "VoiceCampaignResponse", + "VoiceCampaignsCreateResponse", + "VoiceCampaignsCreateResponseVoiceCampaign", + "VoiceCampaignsGetResponse", + "VoiceCampaignsGetResponseVoiceCampaign", + "Wavix", + "WavixEnvironment", + "WebRtcToken", + "WebRtcTokenResponse", + "WebRtcTokensListResponse", + "__version__", + "api_keys", + "billing", + "buy", + "call_control", + "call_recording", + "call_webhooks", + "cart", + "cdrs", + "link_shortener", + "number_validator", + "numbers", + "profile", + "sip_trunks", + "sms_and_mms", + "speech_analytics", + "sub_accounts", + "ten_dlc", + "two_fa", + "voice_campaigns", + "webrtc", +] diff --git a/src/wavix/_default_clients.py b/src/wavix/_default_clients.py new file mode 100644 index 0000000..f3d533e --- /dev/null +++ b/src/wavix/_default_clients.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +SDK_DEFAULT_TIMEOUT = 60 + +try: + import httpx_aiohttp # type: ignore[import-not-found] +except ImportError: + + class DefaultAioHttpClient(httpx.AsyncClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + raise RuntimeError( + "To use the aiohttp client, install the aiohttp extra: pip install wavix-python-sdk[aiohttp]" + ) + +else: + + class DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +class DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: typing.Any) -> None: + kwargs.setdefault("timeout", SDK_DEFAULT_TIMEOUT) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) diff --git a/src/wavix/api_keys/__init__.py b/src/wavix/api_keys/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/api_keys/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/api_keys/client.py b/src/wavix/api_keys/client.py new file mode 100644 index 0000000..326d772 --- /dev/null +++ b/src/wavix/api_keys/client.py @@ -0,0 +1,764 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.api_key import ApiKey +from ..types.api_key_calls_scope_permission import ApiKeyCallsScopePermission +from ..types.api_key_scope_permission import ApiKeyScopePermission +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawApiKeysClient, RawApiKeysClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ApiKeysClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawApiKeysClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawApiKeysClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawApiKeysClient + """ + return self._raw_client + + def list( + self, *, label: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[ApiKey]: + """ + Returns the API keys belonging to the authenticated account. + + Parameters + ---------- + label : typing.Optional[str] + Filters API keys by `label`. Matches partial values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[ApiKey] + Returns the list of API keys. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.api_keys.list( + label="production", + ) + """ + _response = self._raw_client.list(label=label, request_options=request_options) + return _response.data + + def create( + self, + *, + label: str, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKey: + """ + Creates an API key for the authenticated account. Restrict access by listing permitted IP addresses in `permitted_ips`. + + Parameters + ---------- + label : str + API key label. + + active : typing.Optional[bool] + Indicates whether the API key should be activated upon creation. + + restricted : typing.Optional[bool] + Indicates whether to restrict API key access by IP address. When enabled, only requests from IP addresses listed in `permitted_ips` are allowed. + + permitted_ips : typing.Optional[typing.Sequence[str]] + List of permitted IP addresses for this API key. Each must be a valid IPv4 address. Required when `restricted` is true. + + scopes_enabled : typing.Optional[bool] + When `true`, scope fields below are enforced. When `false` (default), the + key has full access. Omitted scope fields default to `{ allow: none }`, + so with `scopes_enabled: true` and no scopes set the key has no access. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKey + Returns the created API key. + + Examples + -------- + from wavix import ApiKeyCallsScopePermission, ApiKeyScopePermission, Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.api_keys.create( + label="Production API Key", + active=True, + restricted=True, + permitted_ips=["192.168.1.1", "10.0.0.1"], + scopes_enabled=True, + numbers=ApiKeyScopePermission( + allow="read", + ), + calls=ApiKeyCallsScopePermission( + allow="read", + ), + messages=ApiKeyScopePermission( + allow="write", + ), + two_fa=ApiKeyScopePermission( + allow="write", + ), + billing=ApiKeyScopePermission( + allow="read", + ), + ) + """ + _response = self._raw_client.create( + label=label, + active=active, + restricted=restricted, + permitted_ips=permitted_ips, + scopes_enabled=scopes_enabled, + numbers=numbers, + trunks=trunks, + calls=calls, + messages=messages, + recordings=recordings, + campaigns=campaigns, + two_fa=two_fa, + validator=validator, + webhooks=webhooks, + embeddable=embeddable, + billing=billing, + account=account, + subaccounts=subaccounts, + request_options=request_options, + ) + return _response.data + + def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the API key identified by `id`. Deletion is permanent and revokes the key immediately. + + Parameters + ---------- + id : int + The unique ID of the API key. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The API key is revoked. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.api_keys.delete( + id=1, + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + def update( + self, + id: int, + *, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + label: typing.Optional[str] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKey: + """ + Updates an API key identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the API key. + + active : typing.Optional[bool] + Indicates whether the API key is active. + + restricted : typing.Optional[bool] + Indicates whether the API key is restricted to the listed permitted IPs. + + scopes_enabled : typing.Optional[bool] + Indicates whether per-resource scope permissions are enforced for the API key. + + permitted_ips : typing.Optional[typing.Sequence[str]] + IP addresses allowed to use the API key when restriction is enabled. + + label : typing.Optional[str] + Human-readable label for the API key. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKey + Returns the updated API key. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.api_keys.update( + id=1, + active=True, + restricted=True, + scopes_enabled=True, + permitted_ips=["192.168.1.1", "10.0.0.1"], + label="Production API Key", + ) + """ + _response = self._raw_client.update( + id, + active=active, + restricted=restricted, + scopes_enabled=scopes_enabled, + permitted_ips=permitted_ips, + label=label, + numbers=numbers, + trunks=trunks, + calls=calls, + messages=messages, + recordings=recordings, + campaigns=campaigns, + two_fa=two_fa, + validator=validator, + webhooks=webhooks, + embeddable=embeddable, + billing=billing, + account=account, + subaccounts=subaccounts, + request_options=request_options, + ) + return _response.data + + +class AsyncApiKeysClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawApiKeysClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawApiKeysClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawApiKeysClient + """ + return self._raw_client + + async def list( + self, *, label: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[ApiKey]: + """ + Returns the API keys belonging to the authenticated account. + + Parameters + ---------- + label : typing.Optional[str] + Filters API keys by `label`. Matches partial values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[ApiKey] + Returns the list of API keys. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.api_keys.list( + label="production", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(label=label, request_options=request_options) + return _response.data + + async def create( + self, + *, + label: str, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKey: + """ + Creates an API key for the authenticated account. Restrict access by listing permitted IP addresses in `permitted_ips`. + + Parameters + ---------- + label : str + API key label. + + active : typing.Optional[bool] + Indicates whether the API key should be activated upon creation. + + restricted : typing.Optional[bool] + Indicates whether to restrict API key access by IP address. When enabled, only requests from IP addresses listed in `permitted_ips` are allowed. + + permitted_ips : typing.Optional[typing.Sequence[str]] + List of permitted IP addresses for this API key. Each must be a valid IPv4 address. Required when `restricted` is true. + + scopes_enabled : typing.Optional[bool] + When `true`, scope fields below are enforced. When `false` (default), the + key has full access. Omitted scope fields default to `{ allow: none }`, + so with `scopes_enabled: true` and no scopes set the key has no access. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKey + Returns the created API key. + + Examples + -------- + import asyncio + + from wavix import ApiKeyCallsScopePermission, ApiKeyScopePermission, AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.api_keys.create( + label="Production API Key", + active=True, + restricted=True, + permitted_ips=["192.168.1.1", "10.0.0.1"], + scopes_enabled=True, + numbers=ApiKeyScopePermission( + allow="read", + ), + calls=ApiKeyCallsScopePermission( + allow="read", + ), + messages=ApiKeyScopePermission( + allow="write", + ), + two_fa=ApiKeyScopePermission( + allow="write", + ), + billing=ApiKeyScopePermission( + allow="read", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + label=label, + active=active, + restricted=restricted, + permitted_ips=permitted_ips, + scopes_enabled=scopes_enabled, + numbers=numbers, + trunks=trunks, + calls=calls, + messages=messages, + recordings=recordings, + campaigns=campaigns, + two_fa=two_fa, + validator=validator, + webhooks=webhooks, + embeddable=embeddable, + billing=billing, + account=account, + subaccounts=subaccounts, + request_options=request_options, + ) + return _response.data + + async def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the API key identified by `id`. Deletion is permanent and revokes the key immediately. + + Parameters + ---------- + id : int + The unique ID of the API key. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The API key is revoked. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.api_keys.delete( + id=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data + + async def update( + self, + id: int, + *, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + label: typing.Optional[str] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ApiKey: + """ + Updates an API key identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the API key. + + active : typing.Optional[bool] + Indicates whether the API key is active. + + restricted : typing.Optional[bool] + Indicates whether the API key is restricted to the listed permitted IPs. + + scopes_enabled : typing.Optional[bool] + Indicates whether per-resource scope permissions are enforced for the API key. + + permitted_ips : typing.Optional[typing.Sequence[str]] + IP addresses allowed to use the API key when restriction is enabled. + + label : typing.Optional[str] + Human-readable label for the API key. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ApiKey + Returns the updated API key. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.api_keys.update( + id=1, + active=True, + restricted=True, + scopes_enabled=True, + permitted_ips=["192.168.1.1", "10.0.0.1"], + label="Production API Key", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, + active=active, + restricted=restricted, + scopes_enabled=scopes_enabled, + permitted_ips=permitted_ips, + label=label, + numbers=numbers, + trunks=trunks, + calls=calls, + messages=messages, + recordings=recordings, + campaigns=campaigns, + two_fa=two_fa, + validator=validator, + webhooks=webhooks, + embeddable=embeddable, + billing=billing, + account=account, + subaccounts=subaccounts, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/api_keys/raw_client.py b/src/wavix/api_keys/raw_client.py new file mode 100644 index 0000000..c85c418 --- /dev/null +++ b/src/wavix/api_keys/raw_client.py @@ -0,0 +1,1191 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.api_key import ApiKey +from ..types.api_key_calls_scope_permission import ApiKeyCallsScopePermission +from ..types.api_key_scope_permission import ApiKeyScopePermission +from ..types.success_response import SuccessResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawApiKeysClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, *, label: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[ApiKey]]: + """ + Returns the API keys belonging to the authenticated account. + + Parameters + ---------- + label : typing.Optional[str] + Filters API keys by `label`. Matches partial values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[ApiKey]] + Returns the list of API keys. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/api-keys", + method="GET", + params={ + "label": label, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[ApiKey], + parse_obj_as( + type_=typing.List[ApiKey], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + label: str, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ApiKey]: + """ + Creates an API key for the authenticated account. Restrict access by listing permitted IP addresses in `permitted_ips`. + + Parameters + ---------- + label : str + API key label. + + active : typing.Optional[bool] + Indicates whether the API key should be activated upon creation. + + restricted : typing.Optional[bool] + Indicates whether to restrict API key access by IP address. When enabled, only requests from IP addresses listed in `permitted_ips` are allowed. + + permitted_ips : typing.Optional[typing.Sequence[str]] + List of permitted IP addresses for this API key. Each must be a valid IPv4 address. Required when `restricted` is true. + + scopes_enabled : typing.Optional[bool] + When `true`, scope fields below are enforced. When `false` (default), the + key has full access. Omitted scope fields default to `{ allow: none }`, + so with `scopes_enabled: true` and no scopes set the key has no access. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ApiKey] + Returns the created API key. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/api-keys", + method="POST", + json={ + "label": label, + "active": active, + "restricted": restricted, + "permitted_ips": permitted_ips, + "scopes_enabled": scopes_enabled, + "numbers": convert_and_respect_annotation_metadata( + object_=numbers, annotation=ApiKeyScopePermission, direction="write" + ), + "trunks": convert_and_respect_annotation_metadata( + object_=trunks, annotation=ApiKeyScopePermission, direction="write" + ), + "calls": convert_and_respect_annotation_metadata( + object_=calls, annotation=ApiKeyCallsScopePermission, direction="write" + ), + "messages": convert_and_respect_annotation_metadata( + object_=messages, annotation=ApiKeyScopePermission, direction="write" + ), + "recordings": convert_and_respect_annotation_metadata( + object_=recordings, annotation=ApiKeyScopePermission, direction="write" + ), + "campaigns": convert_and_respect_annotation_metadata( + object_=campaigns, annotation=ApiKeyScopePermission, direction="write" + ), + "two_fa": convert_and_respect_annotation_metadata( + object_=two_fa, annotation=ApiKeyScopePermission, direction="write" + ), + "validator": convert_and_respect_annotation_metadata( + object_=validator, annotation=ApiKeyScopePermission, direction="write" + ), + "webhooks": convert_and_respect_annotation_metadata( + object_=webhooks, annotation=ApiKeyScopePermission, direction="write" + ), + "embeddable": convert_and_respect_annotation_metadata( + object_=embeddable, annotation=ApiKeyScopePermission, direction="write" + ), + "billing": convert_and_respect_annotation_metadata( + object_=billing, annotation=ApiKeyScopePermission, direction="write" + ), + "account": convert_and_respect_annotation_metadata( + object_=account, annotation=ApiKeyScopePermission, direction="write" + ), + "subaccounts": convert_and_respect_annotation_metadata( + object_=subaccounts, annotation=ApiKeyScopePermission, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKey, + parse_obj_as( + type_=ApiKey, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Deletes the API key identified by `id`. Deletion is permanent and revokes the key immediately. + + Parameters + ---------- + id : int + The unique ID of the API key. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The API key is revoked. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/api-keys/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + id: int, + *, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + label: typing.Optional[str] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ApiKey]: + """ + Updates an API key identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the API key. + + active : typing.Optional[bool] + Indicates whether the API key is active. + + restricted : typing.Optional[bool] + Indicates whether the API key is restricted to the listed permitted IPs. + + scopes_enabled : typing.Optional[bool] + Indicates whether per-resource scope permissions are enforced for the API key. + + permitted_ips : typing.Optional[typing.Sequence[str]] + IP addresses allowed to use the API key when restriction is enabled. + + label : typing.Optional[str] + Human-readable label for the API key. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ApiKey] + Returns the updated API key. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/api-keys/{encode_path_param(id)}", + method="PATCH", + json={ + "active": active, + "restricted": restricted, + "scopes_enabled": scopes_enabled, + "permitted_ips": permitted_ips, + "label": label, + "numbers": convert_and_respect_annotation_metadata( + object_=numbers, annotation=ApiKeyScopePermission, direction="write" + ), + "trunks": convert_and_respect_annotation_metadata( + object_=trunks, annotation=ApiKeyScopePermission, direction="write" + ), + "calls": convert_and_respect_annotation_metadata( + object_=calls, annotation=ApiKeyCallsScopePermission, direction="write" + ), + "messages": convert_and_respect_annotation_metadata( + object_=messages, annotation=ApiKeyScopePermission, direction="write" + ), + "recordings": convert_and_respect_annotation_metadata( + object_=recordings, annotation=ApiKeyScopePermission, direction="write" + ), + "campaigns": convert_and_respect_annotation_metadata( + object_=campaigns, annotation=ApiKeyScopePermission, direction="write" + ), + "two_fa": convert_and_respect_annotation_metadata( + object_=two_fa, annotation=ApiKeyScopePermission, direction="write" + ), + "validator": convert_and_respect_annotation_metadata( + object_=validator, annotation=ApiKeyScopePermission, direction="write" + ), + "webhooks": convert_and_respect_annotation_metadata( + object_=webhooks, annotation=ApiKeyScopePermission, direction="write" + ), + "embeddable": convert_and_respect_annotation_metadata( + object_=embeddable, annotation=ApiKeyScopePermission, direction="write" + ), + "billing": convert_and_respect_annotation_metadata( + object_=billing, annotation=ApiKeyScopePermission, direction="write" + ), + "account": convert_and_respect_annotation_metadata( + object_=account, annotation=ApiKeyScopePermission, direction="write" + ), + "subaccounts": convert_and_respect_annotation_metadata( + object_=subaccounts, annotation=ApiKeyScopePermission, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKey, + parse_obj_as( + type_=ApiKey, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawApiKeysClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, label: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[ApiKey]]: + """ + Returns the API keys belonging to the authenticated account. + + Parameters + ---------- + label : typing.Optional[str] + Filters API keys by `label`. Matches partial values. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[ApiKey]] + Returns the list of API keys. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/api-keys", + method="GET", + params={ + "label": label, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[ApiKey], + parse_obj_as( + type_=typing.List[ApiKey], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + label: str, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ApiKey]: + """ + Creates an API key for the authenticated account. Restrict access by listing permitted IP addresses in `permitted_ips`. + + Parameters + ---------- + label : str + API key label. + + active : typing.Optional[bool] + Indicates whether the API key should be activated upon creation. + + restricted : typing.Optional[bool] + Indicates whether to restrict API key access by IP address. When enabled, only requests from IP addresses listed in `permitted_ips` are allowed. + + permitted_ips : typing.Optional[typing.Sequence[str]] + List of permitted IP addresses for this API key. Each must be a valid IPv4 address. Required when `restricted` is true. + + scopes_enabled : typing.Optional[bool] + When `true`, scope fields below are enforced. When `false` (default), the + key has full access. Omitted scope fields default to `{ allow: none }`, + so with `scopes_enabled: true` and no scopes set the key has no access. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ApiKey] + Returns the created API key. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/api-keys", + method="POST", + json={ + "label": label, + "active": active, + "restricted": restricted, + "permitted_ips": permitted_ips, + "scopes_enabled": scopes_enabled, + "numbers": convert_and_respect_annotation_metadata( + object_=numbers, annotation=ApiKeyScopePermission, direction="write" + ), + "trunks": convert_and_respect_annotation_metadata( + object_=trunks, annotation=ApiKeyScopePermission, direction="write" + ), + "calls": convert_and_respect_annotation_metadata( + object_=calls, annotation=ApiKeyCallsScopePermission, direction="write" + ), + "messages": convert_and_respect_annotation_metadata( + object_=messages, annotation=ApiKeyScopePermission, direction="write" + ), + "recordings": convert_and_respect_annotation_metadata( + object_=recordings, annotation=ApiKeyScopePermission, direction="write" + ), + "campaigns": convert_and_respect_annotation_metadata( + object_=campaigns, annotation=ApiKeyScopePermission, direction="write" + ), + "two_fa": convert_and_respect_annotation_metadata( + object_=two_fa, annotation=ApiKeyScopePermission, direction="write" + ), + "validator": convert_and_respect_annotation_metadata( + object_=validator, annotation=ApiKeyScopePermission, direction="write" + ), + "webhooks": convert_and_respect_annotation_metadata( + object_=webhooks, annotation=ApiKeyScopePermission, direction="write" + ), + "embeddable": convert_and_respect_annotation_metadata( + object_=embeddable, annotation=ApiKeyScopePermission, direction="write" + ), + "billing": convert_and_respect_annotation_metadata( + object_=billing, annotation=ApiKeyScopePermission, direction="write" + ), + "account": convert_and_respect_annotation_metadata( + object_=account, annotation=ApiKeyScopePermission, direction="write" + ), + "subaccounts": convert_and_respect_annotation_metadata( + object_=subaccounts, annotation=ApiKeyScopePermission, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKey, + parse_obj_as( + type_=ApiKey, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Deletes the API key identified by `id`. Deletion is permanent and revokes the key immediately. + + Parameters + ---------- + id : int + The unique ID of the API key. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The API key is revoked. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/api-keys/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + id: int, + *, + active: typing.Optional[bool] = OMIT, + restricted: typing.Optional[bool] = OMIT, + scopes_enabled: typing.Optional[bool] = OMIT, + permitted_ips: typing.Optional[typing.Sequence[str]] = OMIT, + label: typing.Optional[str] = OMIT, + numbers: typing.Optional[ApiKeyScopePermission] = OMIT, + trunks: typing.Optional[ApiKeyScopePermission] = OMIT, + calls: typing.Optional[ApiKeyCallsScopePermission] = OMIT, + messages: typing.Optional[ApiKeyScopePermission] = OMIT, + recordings: typing.Optional[ApiKeyScopePermission] = OMIT, + campaigns: typing.Optional[ApiKeyScopePermission] = OMIT, + two_fa: typing.Optional[ApiKeyScopePermission] = OMIT, + validator: typing.Optional[ApiKeyScopePermission] = OMIT, + webhooks: typing.Optional[ApiKeyScopePermission] = OMIT, + embeddable: typing.Optional[ApiKeyScopePermission] = OMIT, + billing: typing.Optional[ApiKeyScopePermission] = OMIT, + account: typing.Optional[ApiKeyScopePermission] = OMIT, + subaccounts: typing.Optional[ApiKeyScopePermission] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ApiKey]: + """ + Updates an API key identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the API key. + + active : typing.Optional[bool] + Indicates whether the API key is active. + + restricted : typing.Optional[bool] + Indicates whether the API key is restricted to the listed permitted IPs. + + scopes_enabled : typing.Optional[bool] + Indicates whether per-resource scope permissions are enforced for the API key. + + permitted_ips : typing.Optional[typing.Sequence[str]] + IP addresses allowed to use the API key when restriction is enabled. + + label : typing.Optional[str] + Human-readable label for the API key. + + numbers : typing.Optional[ApiKeyScopePermission] + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + + trunks : typing.Optional[ApiKeyScopePermission] + View, create, update, and delete SIP trunks and their settings. + + calls : typing.Optional[ApiKeyCallsScopePermission] + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + + messages : typing.Optional[ApiKeyScopePermission] + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + + recordings : typing.Optional[ApiKeyScopePermission] + List, download, and delete call recordings. + + campaigns : typing.Optional[ApiKeyScopePermission] + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + + two_fa : typing.Optional[ApiKeyScopePermission] + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + + validator : typing.Optional[ApiKeyScopePermission] + View number validation results and trigger single or bulk validation or HLR lookup requests. + + webhooks : typing.Optional[ApiKeyScopePermission] + List, create, and delete webhooks. + + embeddable : typing.Optional[ApiKeyScopePermission] + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + + billing : typing.Optional[ApiKeyScopePermission] + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + + account : typing.Optional[ApiKeyScopePermission] + View and update account profile information and timezone. + + subaccounts : typing.Optional[ApiKeyScopePermission] + Manage subaccounts: list and view them, create, update, and suspend them. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ApiKey] + Returns the updated API key. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/api-keys/{encode_path_param(id)}", + method="PATCH", + json={ + "active": active, + "restricted": restricted, + "scopes_enabled": scopes_enabled, + "permitted_ips": permitted_ips, + "label": label, + "numbers": convert_and_respect_annotation_metadata( + object_=numbers, annotation=ApiKeyScopePermission, direction="write" + ), + "trunks": convert_and_respect_annotation_metadata( + object_=trunks, annotation=ApiKeyScopePermission, direction="write" + ), + "calls": convert_and_respect_annotation_metadata( + object_=calls, annotation=ApiKeyCallsScopePermission, direction="write" + ), + "messages": convert_and_respect_annotation_metadata( + object_=messages, annotation=ApiKeyScopePermission, direction="write" + ), + "recordings": convert_and_respect_annotation_metadata( + object_=recordings, annotation=ApiKeyScopePermission, direction="write" + ), + "campaigns": convert_and_respect_annotation_metadata( + object_=campaigns, annotation=ApiKeyScopePermission, direction="write" + ), + "two_fa": convert_and_respect_annotation_metadata( + object_=two_fa, annotation=ApiKeyScopePermission, direction="write" + ), + "validator": convert_and_respect_annotation_metadata( + object_=validator, annotation=ApiKeyScopePermission, direction="write" + ), + "webhooks": convert_and_respect_annotation_metadata( + object_=webhooks, annotation=ApiKeyScopePermission, direction="write" + ), + "embeddable": convert_and_respect_annotation_metadata( + object_=embeddable, annotation=ApiKeyScopePermission, direction="write" + ), + "billing": convert_and_respect_annotation_metadata( + object_=billing, annotation=ApiKeyScopePermission, direction="write" + ), + "account": convert_and_respect_annotation_metadata( + object_=account, annotation=ApiKeyScopePermission, direction="write" + ), + "subaccounts": convert_and_respect_annotation_metadata( + object_=subaccounts, annotation=ApiKeyScopePermission, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ApiKey, + parse_obj_as( + type_=ApiKey, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/billing/__init__.py b/src/wavix/billing/__init__.py new file mode 100644 index 0000000..fd7ced9 --- /dev/null +++ b/src/wavix/billing/__init__.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import invoices, transactions + from .invoices import ListInvoicesResponse + from .transactions import ListTransactionsResponse +_dynamic_imports: typing.Dict[str, str] = { + "ListInvoicesResponse": ".invoices", + "ListTransactionsResponse": ".transactions", + "invoices": ".invoices", + "transactions": ".transactions", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListInvoicesResponse", "ListTransactionsResponse", "invoices", "transactions"] diff --git a/src/wavix/billing/client.py b/src/wavix/billing/client.py new file mode 100644 index 0000000..83af37c --- /dev/null +++ b/src/wavix/billing/client.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawBillingClient, RawBillingClient + +if typing.TYPE_CHECKING: + from .invoices.client import AsyncInvoicesClient, InvoicesClient + from .transactions.client import AsyncTransactionsClient, TransactionsClient + + +class BillingClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBillingClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transactions: typing.Optional[TransactionsClient] = None + self._invoices: typing.Optional[InvoicesClient] = None + + @property + def with_raw_response(self) -> RawBillingClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBillingClient + """ + return self._raw_client + + @property + def transactions(self): + if self._transactions is None: + from .transactions.client import TransactionsClient # noqa: E402 + + self._transactions = TransactionsClient(client_wrapper=self._client_wrapper) + return self._transactions + + @property + def invoices(self): + if self._invoices is None: + from .invoices.client import InvoicesClient # noqa: E402 + + self._invoices = InvoicesClient(client_wrapper=self._client_wrapper) + return self._invoices + + +class AsyncBillingClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBillingClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transactions: typing.Optional[AsyncTransactionsClient] = None + self._invoices: typing.Optional[AsyncInvoicesClient] = None + + @property + def with_raw_response(self) -> AsyncRawBillingClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBillingClient + """ + return self._raw_client + + @property + def transactions(self): + if self._transactions is None: + from .transactions.client import AsyncTransactionsClient # noqa: E402 + + self._transactions = AsyncTransactionsClient(client_wrapper=self._client_wrapper) + return self._transactions + + @property + def invoices(self): + if self._invoices is None: + from .invoices.client import AsyncInvoicesClient # noqa: E402 + + self._invoices = AsyncInvoicesClient(client_wrapper=self._client_wrapper) + return self._invoices diff --git a/src/wavix/billing/invoices/__init__.py b/src/wavix/billing/invoices/__init__.py new file mode 100644 index 0000000..7150d6b --- /dev/null +++ b/src/wavix/billing/invoices/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListInvoicesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListInvoicesResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListInvoicesResponse"] diff --git a/src/wavix/billing/invoices/client.py b/src/wavix/billing/invoices/client.py new file mode 100644 index 0000000..91ea347 --- /dev/null +++ b/src/wavix/billing/invoices/client.py @@ -0,0 +1,203 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawInvoicesClient, RawInvoicesClient +from .types.list_invoices_response import ListInvoicesResponse + + +class InvoicesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawInvoicesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawInvoicesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawInvoicesClient + """ + return self._raw_client + + def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListInvoicesResponse: + """ + Returns the auto-generated financial statements for the authenticated account, paginated and ordered by billing period. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListInvoicesResponse + Returns a paginated list of financial statements. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.billing.invoices.list( + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list(page=page, per_page=per_page, request_options=request_options) + return _response.data + + def download(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> typing.Iterator[bytes]: + """ + Returns the financial statement identified by `id` as a PDF file. + + Parameters + ---------- + id : int + The unique ID of the financial statement to download. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[bytes] + Returns the financial statement as a PDF file. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.billing.invoices.download( + id=1, + ) + """ + with self._raw_client.download(id, request_options=request_options) as r: + yield from r.data + + +class AsyncInvoicesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawInvoicesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawInvoicesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawInvoicesClient + """ + return self._raw_client + + async def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListInvoicesResponse: + """ + Returns the auto-generated financial statements for the authenticated account, paginated and ordered by billing period. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListInvoicesResponse + Returns a paginated list of financial statements. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.billing.invoices.list( + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(page=page, per_page=per_page, request_options=request_options) + return _response.data + + async def download( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[bytes]: + """ + Returns the financial statement identified by `id` as a PDF file. + + Parameters + ---------- + id : int + The unique ID of the financial statement to download. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[bytes] + Returns the financial statement as a PDF file. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.billing.invoices.download( + id=1, + ) + + + asyncio.run(main()) + """ + async with self._raw_client.download(id, request_options=request_options) as r: + async for _chunk in r.data: + yield _chunk diff --git a/src/wavix/billing/invoices/raw_client.py b/src/wavix/billing/invoices/raw_client.py new file mode 100644 index 0000000..cf0f3b2 --- /dev/null +++ b/src/wavix/billing/invoices/raw_client.py @@ -0,0 +1,302 @@ +# This file was auto-generated by Fern from our API Definition. + +import contextlib +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from .types.list_invoices_response import ListInvoicesResponse +from pydantic import ValidationError + + +class RawInvoicesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListInvoicesResponse]: + """ + Returns the auto-generated financial statements for the authenticated account, paginated and ordered by billing period. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListInvoicesResponse] + Returns a paginated list of financial statements. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/billing/invoices", + method="GET", + params={ + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListInvoicesResponse, + parse_obj_as( + type_=ListInvoicesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + @contextlib.contextmanager + def download( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + """ + Returns the financial statement identified by `id` as a PDF file. + + Parameters + ---------- + id : int + The unique ID of the financial statement to download. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + Returns the financial statement as a PDF file. + """ + with self._client_wrapper.httpx_client.stream( + f"v1/billing/invoices/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + +class AsyncRawInvoicesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListInvoicesResponse]: + """ + Returns the auto-generated financial statements for the authenticated account, paginated and ordered by billing period. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListInvoicesResponse] + Returns a paginated list of financial statements. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/billing/invoices", + method="GET", + params={ + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListInvoicesResponse, + parse_obj_as( + type_=ListInvoicesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + @contextlib.asynccontextmanager + async def download( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + """ + Returns the financial statement identified by `id` as a PDF file. + + Parameters + ---------- + id : int + The unique ID of the financial statement to download. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + Returns the financial statement as a PDF file. + """ + async with self._client_wrapper.httpx_client.stream( + f"v1/billing/invoices/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() diff --git a/src/wavix/billing/invoices/types/__init__.py b/src/wavix/billing/invoices/types/__init__.py new file mode 100644 index 0000000..cc7d01d --- /dev/null +++ b/src/wavix/billing/invoices/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_invoices_response import ListInvoicesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListInvoicesResponse": ".list_invoices_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListInvoicesResponse"] diff --git a/src/wavix/billing/invoices/types/list_invoices_response.py b/src/wavix/billing/invoices/types/list_invoices_response.py new file mode 100644 index 0000000..7ca2755 --- /dev/null +++ b/src/wavix/billing/invoices/types/list_invoices_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.invoice import Invoice +from ....types.pagination import Pagination + + +class ListInvoicesResponse(UniversalBaseModel): + is_empty: bool = pydantic.Field() + """ + Indicates whether the statement list is empty. + """ + + invoices: typing.List[Invoice] = pydantic.Field() + """ + Auto-generated statements for the account. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/billing/raw_client.py b/src/wavix/billing/raw_client.py new file mode 100644 index 0000000..1a8db45 --- /dev/null +++ b/src/wavix/billing/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawBillingClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawBillingClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/billing/transactions/__init__.py b/src/wavix/billing/transactions/__init__.py new file mode 100644 index 0000000..9bdc740 --- /dev/null +++ b/src/wavix/billing/transactions/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListTransactionsResponse +_dynamic_imports: typing.Dict[str, str] = {"ListTransactionsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListTransactionsResponse"] diff --git a/src/wavix/billing/transactions/client.py b/src/wavix/billing/transactions/client.py new file mode 100644 index 0000000..d7f8161 --- /dev/null +++ b/src/wavix/billing/transactions/client.py @@ -0,0 +1,209 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.transaction_type import TransactionType +from .raw_client import AsyncRawTransactionsClient, RawTransactionsClient +from .types.list_transactions_response import ListTransactionsResponse + + +class TransactionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTransactionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTransactionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTransactionsClient + """ + return self._raw_client + + def list( + self, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[TransactionType] = None, + details_contains: typing.Optional[str] = None, + payments: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListTransactionsResponse: + """ + Returns a paginated list of billing transactions for the authenticated account within the requested date range. + + Parameters + ---------- + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[TransactionType] + Filters transactions by type. Accepts a `TransactionType` value. + + details_contains : typing.Optional[str] + Filters transactions whose `details` contain the given substring. + + payments : typing.Optional[bool] + When `true`, returns only account top-up transactions. Defaults to all transaction types. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListTransactionsResponse + Returns a paginated list of billing transactions. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.billing.transactions.list( + from_date=datetime.date.fromisoformat( + "2023-08-01", + ), + to_date=datetime.date.fromisoformat( + "2023-08-31", + ), + details_contains="monthly", + payments=True, + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + from_date=from_date, + to_date=to_date, + type=type, + details_contains=details_contains, + payments=payments, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + +class AsyncTransactionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTransactionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTransactionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTransactionsClient + """ + return self._raw_client + + async def list( + self, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[TransactionType] = None, + details_contains: typing.Optional[str] = None, + payments: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListTransactionsResponse: + """ + Returns a paginated list of billing transactions for the authenticated account within the requested date range. + + Parameters + ---------- + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[TransactionType] + Filters transactions by type. Accepts a `TransactionType` value. + + details_contains : typing.Optional[str] + Filters transactions whose `details` contain the given substring. + + payments : typing.Optional[bool] + When `true`, returns only account top-up transactions. Defaults to all transaction types. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListTransactionsResponse + Returns a paginated list of billing transactions. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.billing.transactions.list( + from_date=datetime.date.fromisoformat( + "2023-08-01", + ), + to_date=datetime.date.fromisoformat( + "2023-08-31", + ), + details_contains="monthly", + payments=True, + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + from_date=from_date, + to_date=to_date, + type=type, + details_contains=details_contains, + payments=payments, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/billing/transactions/raw_client.py b/src/wavix/billing/transactions/raw_client.py new file mode 100644 index 0000000..a7459c5 --- /dev/null +++ b/src/wavix/billing/transactions/raw_client.py @@ -0,0 +1,253 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.service_unavailable_error import ServiceUnavailableError +from ...types.transaction_type import TransactionType +from ...types.validation_error_response import ValidationErrorResponse +from .types.list_transactions_response import ListTransactionsResponse +from pydantic import ValidationError + + +class RawTransactionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[TransactionType] = None, + details_contains: typing.Optional[str] = None, + payments: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListTransactionsResponse]: + """ + Returns a paginated list of billing transactions for the authenticated account within the requested date range. + + Parameters + ---------- + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[TransactionType] + Filters transactions by type. Accepts a `TransactionType` value. + + details_contains : typing.Optional[str] + Filters transactions whose `details` contain the given substring. + + payments : typing.Optional[bool] + When `true`, returns only account top-up transactions. Defaults to all transaction types. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListTransactionsResponse] + Returns a paginated list of billing transactions. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/billing/transactions", + method="GET", + params={ + "from_date": str(from_date), + "to_date": str(to_date), + "type": type, + "details_contains": details_contains, + "payments": payments, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListTransactionsResponse, + parse_obj_as( + type_=ListTransactionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawTransactionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[TransactionType] = None, + details_contains: typing.Optional[str] = None, + payments: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListTransactionsResponse]: + """ + Returns a paginated list of billing transactions for the authenticated account within the requested date range. + + Parameters + ---------- + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[TransactionType] + Filters transactions by type. Accepts a `TransactionType` value. + + details_contains : typing.Optional[str] + Filters transactions whose `details` contain the given substring. + + payments : typing.Optional[bool] + When `true`, returns only account top-up transactions. Defaults to all transaction types. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListTransactionsResponse] + Returns a paginated list of billing transactions. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/billing/transactions", + method="GET", + params={ + "from_date": str(from_date), + "to_date": str(to_date), + "type": type, + "details_contains": details_contains, + "payments": payments, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListTransactionsResponse, + parse_obj_as( + type_=ListTransactionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/billing/transactions/types/__init__.py b/src/wavix/billing/transactions/types/__init__.py new file mode 100644 index 0000000..9866956 --- /dev/null +++ b/src/wavix/billing/transactions/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_transactions_response import ListTransactionsResponse +_dynamic_imports: typing.Dict[str, str] = {"ListTransactionsResponse": ".list_transactions_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListTransactionsResponse"] diff --git a/src/wavix/billing/transactions/types/list_transactions_response.py b/src/wavix/billing/transactions/types/list_transactions_response.py new file mode 100644 index 0000000..5388010 --- /dev/null +++ b/src/wavix/billing/transactions/types/list_transactions_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.financial_transaction import FinancialTransaction +from ....types.pagination import Pagination + + +class ListTransactionsResponse(UniversalBaseModel): + is_empty: bool = pydantic.Field() + """ + Indicates whether there are no transactions. + """ + + transactions: typing.List[FinancialTransaction] = pydantic.Field() + """ + List of transactions. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/buy/__init__.py b/src/wavix/buy/__init__.py new file mode 100644 index 0000000..0e17565 --- /dev/null +++ b/src/wavix/buy/__init__.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import cities, countries, numbers, region_cities, regions + from .cities import ListCitiesResponse + from .countries import ListCountriesResponse + from .numbers import ListNumbersResponse + from .region_cities import ListRegionCitiesResponse + from .regions import ListRegionsResponse +_dynamic_imports: typing.Dict[str, str] = { + "ListCitiesResponse": ".cities", + "ListCountriesResponse": ".countries", + "ListNumbersResponse": ".numbers", + "ListRegionCitiesResponse": ".region_cities", + "ListRegionsResponse": ".regions", + "cities": ".cities", + "countries": ".countries", + "numbers": ".numbers", + "region_cities": ".region_cities", + "regions": ".regions", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ListCitiesResponse", + "ListCountriesResponse", + "ListNumbersResponse", + "ListRegionCitiesResponse", + "ListRegionsResponse", + "cities", + "countries", + "numbers", + "region_cities", + "regions", +] diff --git a/src/wavix/buy/cities/__init__.py b/src/wavix/buy/cities/__init__.py new file mode 100644 index 0000000..b193890 --- /dev/null +++ b/src/wavix/buy/cities/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListCitiesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListCitiesResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListCitiesResponse"] diff --git a/src/wavix/buy/cities/client.py b/src/wavix/buy/cities/client.py new file mode 100644 index 0000000..673ac6d --- /dev/null +++ b/src/wavix/buy/cities/client.py @@ -0,0 +1,134 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawCitiesClient, RawCitiesClient +from .types.list_cities_response import ListCitiesResponse + + +class CitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCitiesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCitiesClient + """ + return self._raw_client + + def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCitiesResponse: + """ + Returns a list of cities for countries where + `has_provinces_or_states` is `false`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCitiesResponse + Returns the list of cities. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.buy.cities.list( + country_id=1891, + ) + """ + _response = self._raw_client.list( + country_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data + + +class AsyncCitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCitiesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCitiesClient + """ + return self._raw_client + + async def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCitiesResponse: + """ + Returns a list of cities for countries where + `has_provinces_or_states` is `false`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCitiesResponse + Returns the list of cities. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.buy.cities.list( + country_id=1891, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + country_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data diff --git a/src/wavix/buy/cities/raw_client.py b/src/wavix/buy/cities/raw_client.py new file mode 100644 index 0000000..6ce7b98 --- /dev/null +++ b/src/wavix/buy/cities/raw_client.py @@ -0,0 +1,201 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from .types.list_cities_response import ListCitiesResponse +from pydantic import ValidationError + + +class RawCitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListCitiesResponse]: + """ + Returns a list of cities for countries where + `has_provinces_or_states` is `false`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListCitiesResponse] + Returns the list of cities. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/cities", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCitiesResponse, + parse_obj_as( + type_=ListCitiesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListCitiesResponse]: + """ + Returns a list of cities for countries where + `has_provinces_or_states` is `false`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListCitiesResponse] + Returns the list of cities. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/cities", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCitiesResponse, + parse_obj_as( + type_=ListCitiesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/buy/cities/types/__init__.py b/src/wavix/buy/cities/types/__init__.py new file mode 100644 index 0000000..3b68590 --- /dev/null +++ b/src/wavix/buy/cities/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_cities_response import ListCitiesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListCitiesResponse": ".list_cities_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListCitiesResponse"] diff --git a/src/wavix/buy/cities/types/list_cities_response.py b/src/wavix/buy/cities/types/list_cities_response.py new file mode 100644 index 0000000..5283589 --- /dev/null +++ b/src/wavix/buy/cities/types/list_cities_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.city import City + + +class ListCitiesResponse(UniversalBaseModel): + cities: typing.List[City] = pydantic.Field() + """ + Cities available for the requested country or region. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/buy/client.py b/src/wavix/buy/client.py new file mode 100644 index 0000000..d92967a --- /dev/null +++ b/src/wavix/buy/client.py @@ -0,0 +1,139 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawBuyClient, RawBuyClient + +if typing.TYPE_CHECKING: + from .cities.client import AsyncCitiesClient, CitiesClient + from .countries.client import AsyncCountriesClient, CountriesClient + from .numbers.client import AsyncNumbersClient, NumbersClient + from .region_cities.client import AsyncRegionCitiesClient, RegionCitiesClient + from .regions.client import AsyncRegionsClient, RegionsClient + + +class BuyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBuyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._countries: typing.Optional[CountriesClient] = None + self._regions: typing.Optional[RegionsClient] = None + self._cities: typing.Optional[CitiesClient] = None + self._region_cities: typing.Optional[RegionCitiesClient] = None + self._numbers: typing.Optional[NumbersClient] = None + + @property + def with_raw_response(self) -> RawBuyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBuyClient + """ + return self._raw_client + + @property + def countries(self): + if self._countries is None: + from .countries.client import CountriesClient # noqa: E402 + + self._countries = CountriesClient(client_wrapper=self._client_wrapper) + return self._countries + + @property + def regions(self): + if self._regions is None: + from .regions.client import RegionsClient # noqa: E402 + + self._regions = RegionsClient(client_wrapper=self._client_wrapper) + return self._regions + + @property + def cities(self): + if self._cities is None: + from .cities.client import CitiesClient # noqa: E402 + + self._cities = CitiesClient(client_wrapper=self._client_wrapper) + return self._cities + + @property + def region_cities(self): + if self._region_cities is None: + from .region_cities.client import RegionCitiesClient # noqa: E402 + + self._region_cities = RegionCitiesClient(client_wrapper=self._client_wrapper) + return self._region_cities + + @property + def numbers(self): + if self._numbers is None: + from .numbers.client import NumbersClient # noqa: E402 + + self._numbers = NumbersClient(client_wrapper=self._client_wrapper) + return self._numbers + + +class AsyncBuyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBuyClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._countries: typing.Optional[AsyncCountriesClient] = None + self._regions: typing.Optional[AsyncRegionsClient] = None + self._cities: typing.Optional[AsyncCitiesClient] = None + self._region_cities: typing.Optional[AsyncRegionCitiesClient] = None + self._numbers: typing.Optional[AsyncNumbersClient] = None + + @property + def with_raw_response(self) -> AsyncRawBuyClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBuyClient + """ + return self._raw_client + + @property + def countries(self): + if self._countries is None: + from .countries.client import AsyncCountriesClient # noqa: E402 + + self._countries = AsyncCountriesClient(client_wrapper=self._client_wrapper) + return self._countries + + @property + def regions(self): + if self._regions is None: + from .regions.client import AsyncRegionsClient # noqa: E402 + + self._regions = AsyncRegionsClient(client_wrapper=self._client_wrapper) + return self._regions + + @property + def cities(self): + if self._cities is None: + from .cities.client import AsyncCitiesClient # noqa: E402 + + self._cities = AsyncCitiesClient(client_wrapper=self._client_wrapper) + return self._cities + + @property + def region_cities(self): + if self._region_cities is None: + from .region_cities.client import AsyncRegionCitiesClient # noqa: E402 + + self._region_cities = AsyncRegionCitiesClient(client_wrapper=self._client_wrapper) + return self._region_cities + + @property + def numbers(self): + if self._numbers is None: + from .numbers.client import AsyncNumbersClient # noqa: E402 + + self._numbers = AsyncNumbersClient(client_wrapper=self._client_wrapper) + return self._numbers diff --git a/src/wavix/buy/countries/__init__.py b/src/wavix/buy/countries/__init__.py new file mode 100644 index 0000000..9551063 --- /dev/null +++ b/src/wavix/buy/countries/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListCountriesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListCountriesResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListCountriesResponse"] diff --git a/src/wavix/buy/countries/client.py b/src/wavix/buy/countries/client.py new file mode 100644 index 0000000..4afae52 --- /dev/null +++ b/src/wavix/buy/countries/client.py @@ -0,0 +1,116 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawCountriesClient, RawCountriesClient +from .types.list_countries_response import ListCountriesResponse + + +class CountriesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCountriesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCountriesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCountriesClient + """ + return self._raw_client + + def list( + self, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCountriesResponse: + """ + Returns a list of countries where phone numbers are available. + + Parameters + ---------- + text_enabled_only : typing.Optional[bool] + When `true`, returns only countries that offer text-enabled phone numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCountriesResponse + Returns the list of countries with available phone numbers. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.buy.countries.list() + """ + _response = self._raw_client.list(text_enabled_only=text_enabled_only, request_options=request_options) + return _response.data + + +class AsyncCountriesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCountriesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCountriesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCountriesClient + """ + return self._raw_client + + async def list( + self, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCountriesResponse: + """ + Returns a list of countries where phone numbers are available. + + Parameters + ---------- + text_enabled_only : typing.Optional[bool] + When `true`, returns only countries that offer text-enabled phone numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCountriesResponse + Returns the list of countries with available phone numbers. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.buy.countries.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(text_enabled_only=text_enabled_only, request_options=request_options) + return _response.data diff --git a/src/wavix/buy/countries/raw_client.py b/src/wavix/buy/countries/raw_client.py new file mode 100644 index 0000000..3f5cb7f --- /dev/null +++ b/src/wavix/buy/countries/raw_client.py @@ -0,0 +1,144 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from .types.list_countries_response import ListCountriesResponse +from pydantic import ValidationError + + +class RawCountriesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListCountriesResponse]: + """ + Returns a list of countries where phone numbers are available. + + Parameters + ---------- + text_enabled_only : typing.Optional[bool] + When `true`, returns only countries that offer text-enabled phone numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListCountriesResponse] + Returns the list of countries with available phone numbers. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/buy/countries", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCountriesResponse, + parse_obj_as( + type_=ListCountriesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCountriesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListCountriesResponse]: + """ + Returns a list of countries where phone numbers are available. + + Parameters + ---------- + text_enabled_only : typing.Optional[bool] + When `true`, returns only countries that offer text-enabled phone numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListCountriesResponse] + Returns the list of countries with available phone numbers. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/buy/countries", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCountriesResponse, + parse_obj_as( + type_=ListCountriesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/buy/countries/types/__init__.py b/src/wavix/buy/countries/types/__init__.py new file mode 100644 index 0000000..76df3a4 --- /dev/null +++ b/src/wavix/buy/countries/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_countries_response import ListCountriesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListCountriesResponse": ".list_countries_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListCountriesResponse"] diff --git a/src/wavix/buy/countries/types/list_countries_response.py b/src/wavix/buy/countries/types/list_countries_response.py new file mode 100644 index 0000000..d00a933 --- /dev/null +++ b/src/wavix/buy/countries/types/list_countries_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.country import Country + + +class ListCountriesResponse(UniversalBaseModel): + countries: typing.List[Country] = pydantic.Field() + """ + Countries where phone numbers are available. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/buy/numbers/__init__.py b/src/wavix/buy/numbers/__init__.py new file mode 100644 index 0000000..f898bb4 --- /dev/null +++ b/src/wavix/buy/numbers/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListNumbersResponse +_dynamic_imports: typing.Dict[str, str] = {"ListNumbersResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListNumbersResponse"] diff --git a/src/wavix/buy/numbers/client.py b/src/wavix/buy/numbers/client.py new file mode 100644 index 0000000..075844d --- /dev/null +++ b/src/wavix/buy/numbers/client.py @@ -0,0 +1,168 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawNumbersClient, RawNumbersClient +from .types.list_numbers_response import ListNumbersResponse + + +class NumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawNumbersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawNumbersClient + """ + return self._raw_client + + def list( + self, + country_id: int, + city_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListNumbersResponse: + """ + Returns a paginated list of phone numbers available for purchase in the specified city. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + city_id : int + The unique ID of the city. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only text-enabled phone numbers. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListNumbersResponse + Returns a paginated list of available phone numbers. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.buy.numbers.list( + country_id=1, + city_id=1, + ) + """ + _response = self._raw_client.list( + country_id, + city_id, + text_enabled_only=text_enabled_only, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + +class AsyncNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawNumbersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawNumbersClient + """ + return self._raw_client + + async def list( + self, + country_id: int, + city_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListNumbersResponse: + """ + Returns a paginated list of phone numbers available for purchase in the specified city. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + city_id : int + The unique ID of the city. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only text-enabled phone numbers. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListNumbersResponse + Returns a paginated list of available phone numbers. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.buy.numbers.list( + country_id=1, + city_id=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + country_id, + city_id, + text_enabled_only=text_enabled_only, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/buy/numbers/raw_client.py b/src/wavix/buy/numbers/raw_client.py new file mode 100644 index 0000000..360e2d9 --- /dev/null +++ b/src/wavix/buy/numbers/raw_client.py @@ -0,0 +1,227 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from .types.list_numbers_response import ListNumbersResponse +from pydantic import ValidationError + + +class RawNumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + country_id: int, + city_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListNumbersResponse]: + """ + Returns a paginated list of phone numbers available for purchase in the specified city. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + city_id : int + The unique ID of the city. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only text-enabled phone numbers. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListNumbersResponse] + Returns a paginated list of available phone numbers. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/cities/{encode_path_param(city_id)}/dids", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListNumbersResponse, + parse_obj_as( + type_=ListNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + country_id: int, + city_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListNumbersResponse]: + """ + Returns a paginated list of phone numbers available for purchase in the specified city. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + city_id : int + The unique ID of the city. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only text-enabled phone numbers. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListNumbersResponse] + Returns a paginated list of available phone numbers. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/cities/{encode_path_param(city_id)}/dids", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListNumbersResponse, + parse_obj_as( + type_=ListNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/buy/numbers/types/__init__.py b/src/wavix/buy/numbers/types/__init__.py new file mode 100644 index 0000000..d94134b --- /dev/null +++ b/src/wavix/buy/numbers/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_numbers_response import ListNumbersResponse +_dynamic_imports: typing.Dict[str, str] = {"ListNumbersResponse": ".list_numbers_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListNumbersResponse"] diff --git a/src/wavix/buy/numbers/types/list_numbers_response.py b/src/wavix/buy/numbers/types/list_numbers_response.py new file mode 100644 index 0000000..4122a84 --- /dev/null +++ b/src/wavix/buy/numbers/types/list_numbers_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.available_number import AvailableNumber +from ....types.pagination import Pagination + + +class ListNumbersResponse(UniversalBaseModel): + dids: typing.List[AvailableNumber] = pydantic.Field() + """ + Phone numbers available for purchase that match the search criteria. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/buy/raw_client.py b/src/wavix/buy/raw_client.py new file mode 100644 index 0000000..9fc2b11 --- /dev/null +++ b/src/wavix/buy/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawBuyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawBuyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/buy/region_cities/__init__.py b/src/wavix/buy/region_cities/__init__.py new file mode 100644 index 0000000..264a60f --- /dev/null +++ b/src/wavix/buy/region_cities/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListRegionCitiesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListRegionCitiesResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListRegionCitiesResponse"] diff --git a/src/wavix/buy/region_cities/client.py b/src/wavix/buy/region_cities/client.py new file mode 100644 index 0000000..30c2aff --- /dev/null +++ b/src/wavix/buy/region_cities/client.py @@ -0,0 +1,142 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawRegionCitiesClient, RawRegionCitiesClient +from .types.list_region_cities_response import ListRegionCitiesResponse + + +class RegionCitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRegionCitiesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRegionCitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRegionCitiesClient + """ + return self._raw_client + + def list( + self, + country_id: int, + region_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListRegionCitiesResponse: + """ + Returns a list of cities in the specified region for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + region_id : int + The unique ID of the region. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListRegionCitiesResponse + Returns the list of cities. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.buy.region_cities.list( + country_id=1891, + region_id=821, + ) + """ + _response = self._raw_client.list( + country_id, region_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data + + +class AsyncRegionCitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRegionCitiesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRegionCitiesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRegionCitiesClient + """ + return self._raw_client + + async def list( + self, + country_id: int, + region_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListRegionCitiesResponse: + """ + Returns a list of cities in the specified region for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + region_id : int + The unique ID of the region. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListRegionCitiesResponse + Returns the list of cities. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.buy.region_cities.list( + country_id=1891, + region_id=821, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + country_id, region_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data diff --git a/src/wavix/buy/region_cities/raw_client.py b/src/wavix/buy/region_cities/raw_client.py new file mode 100644 index 0000000..f468090 --- /dev/null +++ b/src/wavix/buy/region_cities/raw_client.py @@ -0,0 +1,207 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from .types.list_region_cities_response import ListRegionCitiesResponse +from pydantic import ValidationError + + +class RawRegionCitiesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + country_id: int, + region_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListRegionCitiesResponse]: + """ + Returns a list of cities in the specified region for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + region_id : int + The unique ID of the region. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListRegionCitiesResponse] + Returns the list of cities. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/regions/{encode_path_param(region_id)}/cities", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListRegionCitiesResponse, + parse_obj_as( + type_=ListRegionCitiesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawRegionCitiesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + country_id: int, + region_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListRegionCitiesResponse]: + """ + Returns a list of cities in the specified region for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + region_id : int + The unique ID of the region. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only cities that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListRegionCitiesResponse] + Returns the list of cities. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/regions/{encode_path_param(region_id)}/cities", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListRegionCitiesResponse, + parse_obj_as( + type_=ListRegionCitiesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/buy/region_cities/types/__init__.py b/src/wavix/buy/region_cities/types/__init__.py new file mode 100644 index 0000000..e4584ed --- /dev/null +++ b/src/wavix/buy/region_cities/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_region_cities_response import ListRegionCitiesResponse +_dynamic_imports: typing.Dict[str, str] = {"ListRegionCitiesResponse": ".list_region_cities_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListRegionCitiesResponse"] diff --git a/src/wavix/buy/region_cities/types/list_region_cities_response.py b/src/wavix/buy/region_cities/types/list_region_cities_response.py new file mode 100644 index 0000000..b9584fa --- /dev/null +++ b/src/wavix/buy/region_cities/types/list_region_cities_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.city import City + + +class ListRegionCitiesResponse(UniversalBaseModel): + cities: typing.List[City] = pydantic.Field() + """ + Cities available for the requested country or region. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/buy/regions/__init__.py b/src/wavix/buy/regions/__init__.py new file mode 100644 index 0000000..1de7747 --- /dev/null +++ b/src/wavix/buy/regions/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ListRegionsResponse +_dynamic_imports: typing.Dict[str, str] = {"ListRegionsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListRegionsResponse"] diff --git a/src/wavix/buy/regions/client.py b/src/wavix/buy/regions/client.py new file mode 100644 index 0000000..cd84188 --- /dev/null +++ b/src/wavix/buy/regions/client.py @@ -0,0 +1,132 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawRegionsClient, RawRegionsClient +from .types.list_regions_response import ListRegionsResponse + + +class RegionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawRegionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawRegionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawRegionsClient + """ + return self._raw_client + + def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListRegionsResponse: + """ + Returns a list of regions (states or provinces) for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only regions that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListRegionsResponse + Returns the list of regions. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.buy.regions.list( + country_id=1892, + ) + """ + _response = self._raw_client.list( + country_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data + + +class AsyncRegionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawRegionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawRegionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawRegionsClient + """ + return self._raw_client + + async def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListRegionsResponse: + """ + Returns a list of regions (states or provinces) for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only regions that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListRegionsResponse + Returns the list of regions. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.buy.regions.list( + country_id=1892, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + country_id, text_enabled_only=text_enabled_only, request_options=request_options + ) + return _response.data diff --git a/src/wavix/buy/regions/raw_client.py b/src/wavix/buy/regions/raw_client.py new file mode 100644 index 0000000..b8533e2 --- /dev/null +++ b/src/wavix/buy/regions/raw_client.py @@ -0,0 +1,222 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from .types.list_regions_response import ListRegionsResponse +from pydantic import ValidationError + + +class RawRegionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListRegionsResponse]: + """ + Returns a list of regions (states or provinces) for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only regions that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListRegionsResponse] + Returns the list of regions. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/regions", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListRegionsResponse, + parse_obj_as( + type_=ListRegionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawRegionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + country_id: int, + *, + text_enabled_only: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListRegionsResponse]: + """ + Returns a list of regions (states or provinces) for countries where `has_provinces_or_states` is `true`. + + Parameters + ---------- + country_id : int + The unique ID of the country. + + text_enabled_only : typing.Optional[bool] + When `true`, returns only regions that offer text-enabled numbers. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListRegionsResponse] + Returns the list of regions. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/buy/countries/{encode_path_param(country_id)}/regions", + method="GET", + params={ + "text_enabled_only": text_enabled_only, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListRegionsResponse, + parse_obj_as( + type_=ListRegionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/buy/regions/types/__init__.py b/src/wavix/buy/regions/types/__init__.py new file mode 100644 index 0000000..835a2e8 --- /dev/null +++ b/src/wavix/buy/regions/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_regions_response import ListRegionsResponse +_dynamic_imports: typing.Dict[str, str] = {"ListRegionsResponse": ".list_regions_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ListRegionsResponse"] diff --git a/src/wavix/buy/regions/types/list_regions_response.py b/src/wavix/buy/regions/types/list_regions_response.py new file mode 100644 index 0000000..cfc8be8 --- /dev/null +++ b/src/wavix/buy/regions/types/list_regions_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.region import Region + + +class ListRegionsResponse(UniversalBaseModel): + regions: typing.List[Region] = pydantic.Field() + """ + States or provinces available for the requested country. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/call_control/__init__.py b/src/wavix/call_control/__init__.py new file mode 100644 index 0000000..f292d48 --- /dev/null +++ b/src/wavix/call_control/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CallDtmfCollectRequestPrompt, CallDtmfCollectRequestPromptSay + from . import audio, streams +_dynamic_imports: typing.Dict[str, str] = { + "CallDtmfCollectRequestPrompt": ".types", + "CallDtmfCollectRequestPromptSay": ".types", + "audio": ".audio", + "streams": ".streams", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CallDtmfCollectRequestPrompt", "CallDtmfCollectRequestPromptSay", "audio", "streams"] diff --git a/src/wavix/call_control/audio/__init__.py b/src/wavix/call_control/audio/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/call_control/audio/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/call_control/audio/client.py b/src/wavix/call_control/audio/client.py new file mode 100644 index 0000000..cef7966 --- /dev/null +++ b/src/wavix/call_control/audio/client.py @@ -0,0 +1,195 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.success_response import SuccessResponse +from .raw_client import AsyncRawAudioClient, RawAudioClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class AudioClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawAudioClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawAudioClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawAudioClient + """ + return self._raw_client + + def play( + self, id: str, *, audio_file: str, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Plays an audio prompt into the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + audio_file : str + URL of the audio file to play to the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Audio playback starts. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.audio.play( + id="id", + audio_file="https://examples.com/audio.wav", + ) + """ + _response = self._raw_client.play(id, audio_file=audio_file, request_options=request_options) + return _response.data + + def stop(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Stops audio playback in the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Audio playback stops. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.audio.stop( + id="id", + ) + """ + _response = self._raw_client.stop(id, request_options=request_options) + return _response.data + + +class AsyncAudioClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawAudioClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawAudioClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawAudioClient + """ + return self._raw_client + + async def play( + self, id: str, *, audio_file: str, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Plays an audio prompt into the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + audio_file : str + URL of the audio file to play to the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Audio playback starts. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.audio.play( + id="id", + audio_file="https://examples.com/audio.wav", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.play(id, audio_file=audio_file, request_options=request_options) + return _response.data + + async def stop(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Stops audio playback in the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Audio playback stops. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.audio.stop( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.stop(id, request_options=request_options) + return _response.data diff --git a/src/wavix/call_control/audio/raw_client.py b/src/wavix/call_control/audio/raw_client.py new file mode 100644 index 0000000..d0727fc --- /dev/null +++ b/src/wavix/call_control/audio/raw_client.py @@ -0,0 +1,333 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.not_found_error import NotFoundError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.success_response import SuccessResponse +from ...types.unauthorized_error_response import UnauthorizedErrorResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawAudioClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def play( + self, id: str, *, audio_file: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Plays an audio prompt into the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + audio_file : str + URL of the audio file to play to the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. Audio playback starts. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/play", + method="POST", + json={ + "audio_file": audio_file, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def stop( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Stops audio playback in the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. Audio playback stops. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/audio", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawAudioClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def play( + self, id: str, *, audio_file: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Plays an audio prompt into the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + audio_file : str + URL of the audio file to play to the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. Audio playback starts. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/play", + method="POST", + json={ + "audio_file": audio_file, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def stop( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Stops audio playback in the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. Audio playback stops. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/audio", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/call_control/client.py b/src/wavix/call_control/client.py new file mode 100644 index 0000000..bdf4640 --- /dev/null +++ b/src/wavix/call_control/client.py @@ -0,0 +1,796 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.call_create_response import CallCreateResponse +from ..types.call_list_response import CallListResponse +from ..types.call_response import CallResponse +from ..types.call_stream_channel import CallStreamChannel +from ..types.call_stream_type import CallStreamType +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawCallControlClient, RawCallControlClient +from .types.call_dtmf_collect_request_prompt import CallDtmfCollectRequestPrompt + +if typing.TYPE_CHECKING: + from .audio.client import AsyncAudioClient, AudioClient + from .streams.client import AsyncStreamsClient, StreamsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CallControlClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCallControlClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._streams: typing.Optional[StreamsClient] = None + self._audio: typing.Optional[AudioClient] = None + + @property + def with_raw_response(self) -> RawCallControlClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCallControlClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> CallListResponse: + """ + Returns the calls currently in progress for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallListResponse + Returns the list of active calls. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + from_: str, + to: str, + callback_url: str, + recording: typing.Optional[bool] = OMIT, + voicemail_detection: typing.Optional[bool] = OMIT, + tag: typing.Optional[str] = OMIT, + timeout: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallCreateResponse: + """ + Places an outbound call. Returns the call with its `uuid` for tracking and control. + + Parameters + ---------- + from_ : str + Caller ID. Must be an active or verified phone number on the account. + + to : str + Destination number in E.164 format + + callback_url : str + The callback URL where Wavix sends the call status updates + + recording : typing.Optional[bool] + Specifies whether to record the call + + voicemail_detection : typing.Optional[bool] + Specifies whether the AMD is turned on for the call + + tag : typing.Optional[str] + Call metadata + + timeout : typing.Optional[int] + The ring timeout, in seconds, before the call is considered unanswered. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallCreateResponse + Returns the created call. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.create( + from_="+1234567890", + to="+1987654321", + callback_url="https://examples.com/callback", + ) + """ + _response = self._raw_client.create( + from_=from_, + to=to, + callback_url=callback_url, + recording=recording, + voicemail_detection=voicemail_detection, + tag=tag, + timeout=timeout, + request_options=request_options, + ) + return _response.data + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> CallResponse: + """ + Returns the call identified by `id`, including its current event and timestamps. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallResponse + Returns the call. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Ends the active call identified by `id` by hanging up. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call is ended. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + def update(self, id: str, *, tag: str, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Updates the active call identified by `id`. Only the `tag` field can be changed. + + Parameters + ---------- + id : str + The `uuid` of the call. + + tag : str + User-defined label attached to the Call for tracking or reporting. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call `tag` is updated. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.update( + id="id", + tag="marketing-campaign", + ) + """ + _response = self._raw_client.update(id, tag=tag, request_options=request_options) + return _response.data + + def answer( + self, + id: str, + *, + call_recording: typing.Optional[bool] = OMIT, + call_transcription: typing.Optional[bool] = OMIT, + stream_url: typing.Optional[str] = OMIT, + stream_type: typing.Optional[CallStreamType] = OMIT, + stream_channel: typing.Optional[CallStreamChannel] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Answers the inbound call identified by `id`. Optionally starts media streaming on answer. + + Parameters + ---------- + id : str + The `uuid` of the call. + + call_recording : typing.Optional[bool] + Indicates whether the call should be recorded. + + call_transcription : typing.Optional[bool] + Indicates whether the call should be transcribed after it ends. + + stream_url : typing.Optional[str] + WebSocket URL to stream the call. + + stream_type : typing.Optional[CallStreamType] + Direction of audio streamed to `stream_url`. + + stream_channel : typing.Optional[CallStreamChannel] + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call is answered. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.answer( + id="id", + ) + """ + _response = self._raw_client.answer( + id, + call_recording=call_recording, + call_transcription=call_transcription, + stream_url=stream_url, + stream_type=stream_type, + stream_channel=stream_channel, + request_options=request_options, + ) + return _response.data + + def collect( + self, + id: str, + *, + max_digits: typing.Optional[int] = OMIT, + timeout: typing.Optional[int] = OMIT, + termination_character: typing.Optional[str] = OMIT, + max_attempts: typing.Optional[int] = OMIT, + prompt: typing.Optional[CallDtmfCollectRequestPrompt] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Collects DTMF keypad input from the caller on the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + max_digits : typing.Optional[int] + Maximum number of digits to collect. + + timeout : typing.Optional[int] + Timeout for digit collection in seconds. + + termination_character : typing.Optional[str] + DTMF character that ends input collection. + + max_attempts : typing.Optional[int] + Maximum number of attempts. + + prompt : typing.Optional[CallDtmfCollectRequestPrompt] + Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. DTMF collection starts. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.collect( + id="id", + ) + """ + _response = self._raw_client.collect( + id, + max_digits=max_digits, + timeout=timeout, + termination_character=termination_character, + max_attempts=max_attempts, + prompt=prompt, + request_options=request_options, + ) + return _response.data + + @property + def streams(self): + if self._streams is None: + from .streams.client import StreamsClient # noqa: E402 + + self._streams = StreamsClient(client_wrapper=self._client_wrapper) + return self._streams + + @property + def audio(self): + if self._audio is None: + from .audio.client import AudioClient # noqa: E402 + + self._audio = AudioClient(client_wrapper=self._client_wrapper) + return self._audio + + +class AsyncCallControlClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCallControlClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._streams: typing.Optional[AsyncStreamsClient] = None + self._audio: typing.Optional[AsyncAudioClient] = None + + @property + def with_raw_response(self) -> AsyncRawCallControlClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCallControlClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> CallListResponse: + """ + Returns the calls currently in progress for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallListResponse + Returns the list of active calls. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + from_: str, + to: str, + callback_url: str, + recording: typing.Optional[bool] = OMIT, + voicemail_detection: typing.Optional[bool] = OMIT, + tag: typing.Optional[str] = OMIT, + timeout: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallCreateResponse: + """ + Places an outbound call. Returns the call with its `uuid` for tracking and control. + + Parameters + ---------- + from_ : str + Caller ID. Must be an active or verified phone number on the account. + + to : str + Destination number in E.164 format + + callback_url : str + The callback URL where Wavix sends the call status updates + + recording : typing.Optional[bool] + Specifies whether to record the call + + voicemail_detection : typing.Optional[bool] + Specifies whether the AMD is turned on for the call + + tag : typing.Optional[str] + Call metadata + + timeout : typing.Optional[int] + The ring timeout, in seconds, before the call is considered unanswered. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallCreateResponse + Returns the created call. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.create( + from_="+1234567890", + to="+1987654321", + callback_url="https://examples.com/callback", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + from_=from_, + to=to, + callback_url=callback_url, + recording=recording, + voicemail_detection=voicemail_detection, + tag=tag, + timeout=timeout, + request_options=request_options, + ) + return _response.data + + async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> CallResponse: + """ + Returns the call identified by `id`, including its current event and timestamps. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallResponse + Returns the call. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Ends the active call identified by `id` by hanging up. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call is ended. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data + + async def update( + self, id: str, *, tag: str, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Updates the active call identified by `id`. Only the `tag` field can be changed. + + Parameters + ---------- + id : str + The `uuid` of the call. + + tag : str + User-defined label attached to the Call for tracking or reporting. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call `tag` is updated. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.update( + id="id", + tag="marketing-campaign", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update(id, tag=tag, request_options=request_options) + return _response.data + + async def answer( + self, + id: str, + *, + call_recording: typing.Optional[bool] = OMIT, + call_transcription: typing.Optional[bool] = OMIT, + stream_url: typing.Optional[str] = OMIT, + stream_type: typing.Optional[CallStreamType] = OMIT, + stream_channel: typing.Optional[CallStreamChannel] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Answers the inbound call identified by `id`. Optionally starts media streaming on answer. + + Parameters + ---------- + id : str + The `uuid` of the call. + + call_recording : typing.Optional[bool] + Indicates whether the call should be recorded. + + call_transcription : typing.Optional[bool] + Indicates whether the call should be transcribed after it ends. + + stream_url : typing.Optional[str] + WebSocket URL to stream the call. + + stream_type : typing.Optional[CallStreamType] + Direction of audio streamed to `stream_url`. + + stream_channel : typing.Optional[CallStreamChannel] + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call is answered. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.answer( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.answer( + id, + call_recording=call_recording, + call_transcription=call_transcription, + stream_url=stream_url, + stream_type=stream_type, + stream_channel=stream_channel, + request_options=request_options, + ) + return _response.data + + async def collect( + self, + id: str, + *, + max_digits: typing.Optional[int] = OMIT, + timeout: typing.Optional[int] = OMIT, + termination_character: typing.Optional[str] = OMIT, + max_attempts: typing.Optional[int] = OMIT, + prompt: typing.Optional[CallDtmfCollectRequestPrompt] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Collects DTMF keypad input from the caller on the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + max_digits : typing.Optional[int] + Maximum number of digits to collect. + + timeout : typing.Optional[int] + Timeout for digit collection in seconds. + + termination_character : typing.Optional[str] + DTMF character that ends input collection. + + max_attempts : typing.Optional[int] + Maximum number of attempts. + + prompt : typing.Optional[CallDtmfCollectRequestPrompt] + Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. DTMF collection starts. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.collect( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.collect( + id, + max_digits=max_digits, + timeout=timeout, + termination_character=termination_character, + max_attempts=max_attempts, + prompt=prompt, + request_options=request_options, + ) + return _response.data + + @property + def streams(self): + if self._streams is None: + from .streams.client import AsyncStreamsClient # noqa: E402 + + self._streams = AsyncStreamsClient(client_wrapper=self._client_wrapper) + return self._streams + + @property + def audio(self): + if self._audio is None: + from .audio.client import AsyncAudioClient # noqa: E402 + + self._audio = AsyncAudioClient(client_wrapper=self._client_wrapper) + return self._audio diff --git a/src/wavix/call_control/raw_client.py b/src/wavix/call_control/raw_client.py new file mode 100644 index 0000000..1acc4f0 --- /dev/null +++ b/src/wavix/call_control/raw_client.py @@ -0,0 +1,1224 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.not_found_error import NotFoundError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.call_create_response import CallCreateResponse +from ..types.call_list_response import CallListResponse +from ..types.call_response import CallResponse +from ..types.call_stream_channel import CallStreamChannel +from ..types.call_stream_type import CallStreamType +from ..types.success_response import SuccessResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from .types.call_dtmf_collect_request_prompt import CallDtmfCollectRequestPrompt +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCallControlClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CallListResponse]: + """ + Returns the calls currently in progress for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallListResponse] + Returns the list of active calls. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/calls", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallListResponse, + parse_obj_as( + type_=CallListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + from_: str, + to: str, + callback_url: str, + recording: typing.Optional[bool] = OMIT, + voicemail_detection: typing.Optional[bool] = OMIT, + tag: typing.Optional[str] = OMIT, + timeout: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CallCreateResponse]: + """ + Places an outbound call. Returns the call with its `uuid` for tracking and control. + + Parameters + ---------- + from_ : str + Caller ID. Must be an active or verified phone number on the account. + + to : str + Destination number in E.164 format + + callback_url : str + The callback URL where Wavix sends the call status updates + + recording : typing.Optional[bool] + Specifies whether to record the call + + voicemail_detection : typing.Optional[bool] + Specifies whether the AMD is turned on for the call + + tag : typing.Optional[str] + Call metadata + + timeout : typing.Optional[int] + The ring timeout, in seconds, before the call is considered unanswered. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallCreateResponse] + Returns the created call. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/calls", + method="POST", + json={ + "from": from_, + "to": to, + "callback_url": callback_url, + "recording": recording, + "voicemail_detection": voicemail_detection, + "tag": tag, + "timeout": timeout, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallCreateResponse, + parse_obj_as( + type_=CallCreateResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CallResponse]: + """ + Returns the call identified by `id`, including its current event and timestamps. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallResponse] + Returns the call. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallResponse, + parse_obj_as( + type_=CallResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Ends the active call identified by `id` by hanging up. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The call is ended. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, id: str, *, tag: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Updates the active call identified by `id`. Only the `tag` field can be changed. + + Parameters + ---------- + id : str + The `uuid` of the call. + + tag : str + User-defined label attached to the Call for tracking or reporting. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The call `tag` is updated. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="PATCH", + json={ + "tag": tag, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def answer( + self, + id: str, + *, + call_recording: typing.Optional[bool] = OMIT, + call_transcription: typing.Optional[bool] = OMIT, + stream_url: typing.Optional[str] = OMIT, + stream_type: typing.Optional[CallStreamType] = OMIT, + stream_channel: typing.Optional[CallStreamChannel] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SuccessResponse]: + """ + Answers the inbound call identified by `id`. Optionally starts media streaming on answer. + + Parameters + ---------- + id : str + The `uuid` of the call. + + call_recording : typing.Optional[bool] + Indicates whether the call should be recorded. + + call_transcription : typing.Optional[bool] + Indicates whether the call should be transcribed after it ends. + + stream_url : typing.Optional[str] + WebSocket URL to stream the call. + + stream_type : typing.Optional[CallStreamType] + Direction of audio streamed to `stream_url`. + + stream_channel : typing.Optional[CallStreamChannel] + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The call is answered. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/answer", + method="POST", + json={ + "call_recording": call_recording, + "call_transcription": call_transcription, + "stream_url": stream_url, + "stream_type": stream_type, + "stream_channel": stream_channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def collect( + self, + id: str, + *, + max_digits: typing.Optional[int] = OMIT, + timeout: typing.Optional[int] = OMIT, + termination_character: typing.Optional[str] = OMIT, + max_attempts: typing.Optional[int] = OMIT, + prompt: typing.Optional[CallDtmfCollectRequestPrompt] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SuccessResponse]: + """ + Collects DTMF keypad input from the caller on the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + max_digits : typing.Optional[int] + Maximum number of digits to collect. + + timeout : typing.Optional[int] + Timeout for digit collection in seconds. + + termination_character : typing.Optional[str] + DTMF character that ends input collection. + + max_attempts : typing.Optional[int] + Maximum number of attempts. + + prompt : typing.Optional[CallDtmfCollectRequestPrompt] + Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. DTMF collection starts. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/collect", + method="POST", + json={ + "max_digits": max_digits, + "timeout": timeout, + "termination_character": termination_character, + "max_attempts": max_attempts, + "prompt": convert_and_respect_annotation_metadata( + object_=prompt, annotation=CallDtmfCollectRequestPrompt, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCallControlClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CallListResponse]: + """ + Returns the calls currently in progress for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallListResponse] + Returns the list of active calls. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/calls", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallListResponse, + parse_obj_as( + type_=CallListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + from_: str, + to: str, + callback_url: str, + recording: typing.Optional[bool] = OMIT, + voicemail_detection: typing.Optional[bool] = OMIT, + tag: typing.Optional[str] = OMIT, + timeout: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CallCreateResponse]: + """ + Places an outbound call. Returns the call with its `uuid` for tracking and control. + + Parameters + ---------- + from_ : str + Caller ID. Must be an active or verified phone number on the account. + + to : str + Destination number in E.164 format + + callback_url : str + The callback URL where Wavix sends the call status updates + + recording : typing.Optional[bool] + Specifies whether to record the call + + voicemail_detection : typing.Optional[bool] + Specifies whether the AMD is turned on for the call + + tag : typing.Optional[str] + Call metadata + + timeout : typing.Optional[int] + The ring timeout, in seconds, before the call is considered unanswered. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallCreateResponse] + Returns the created call. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/calls", + method="POST", + json={ + "from": from_, + "to": to, + "callback_url": callback_url, + "recording": recording, + "voicemail_detection": voicemail_detection, + "tag": tag, + "timeout": timeout, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallCreateResponse, + parse_obj_as( + type_=CallCreateResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CallResponse]: + """ + Returns the call identified by `id`, including its current event and timestamps. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallResponse] + Returns the call. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallResponse, + parse_obj_as( + type_=CallResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Ends the active call identified by `id` by hanging up. + + Parameters + ---------- + id : str + The `uuid` of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The call is ended. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, id: str, *, tag: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Updates the active call identified by `id`. Only the `tag` field can be changed. + + Parameters + ---------- + id : str + The `uuid` of the call. + + tag : str + User-defined label attached to the Call for tracking or reporting. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The call `tag` is updated. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}", + method="PATCH", + json={ + "tag": tag, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def answer( + self, + id: str, + *, + call_recording: typing.Optional[bool] = OMIT, + call_transcription: typing.Optional[bool] = OMIT, + stream_url: typing.Optional[str] = OMIT, + stream_type: typing.Optional[CallStreamType] = OMIT, + stream_channel: typing.Optional[CallStreamChannel] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Answers the inbound call identified by `id`. Optionally starts media streaming on answer. + + Parameters + ---------- + id : str + The `uuid` of the call. + + call_recording : typing.Optional[bool] + Indicates whether the call should be recorded. + + call_transcription : typing.Optional[bool] + Indicates whether the call should be transcribed after it ends. + + stream_url : typing.Optional[str] + WebSocket URL to stream the call. + + stream_type : typing.Optional[CallStreamType] + Direction of audio streamed to `stream_url`. + + stream_channel : typing.Optional[CallStreamChannel] + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The call is answered. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/answer", + method="POST", + json={ + "call_recording": call_recording, + "call_transcription": call_transcription, + "stream_url": stream_url, + "stream_type": stream_type, + "stream_channel": stream_channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def collect( + self, + id: str, + *, + max_digits: typing.Optional[int] = OMIT, + timeout: typing.Optional[int] = OMIT, + termination_character: typing.Optional[str] = OMIT, + max_attempts: typing.Optional[int] = OMIT, + prompt: typing.Optional[CallDtmfCollectRequestPrompt] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Collects DTMF keypad input from the caller on the active call identified by `id`. + + Parameters + ---------- + id : str + The `uuid` of the call. + + max_digits : typing.Optional[int] + Maximum number of digits to collect. + + timeout : typing.Optional[int] + Timeout for digit collection in seconds. + + termination_character : typing.Optional[str] + DTMF character that ends input collection. + + max_attempts : typing.Optional[int] + Maximum number of attempts. + + prompt : typing.Optional[CallDtmfCollectRequestPrompt] + Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. DTMF collection starts. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(id)}/collect", + method="POST", + json={ + "max_digits": max_digits, + "timeout": timeout, + "termination_character": termination_character, + "max_attempts": max_attempts, + "prompt": convert_and_respect_annotation_metadata( + object_=prompt, annotation=CallDtmfCollectRequestPrompt, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/call_control/streams/__init__.py b/src/wavix/call_control/streams/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/call_control/streams/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/call_control/streams/client.py b/src/wavix/call_control/streams/client.py new file mode 100644 index 0000000..a46cfd3 --- /dev/null +++ b/src/wavix/call_control/streams/client.py @@ -0,0 +1,250 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.call_stream_channel import CallStreamChannel +from ...types.call_stream_response import CallStreamResponse +from ...types.call_stream_type import CallStreamType +from ...types.success_response import SuccessResponse +from .raw_client import AsyncRawStreamsClient, RawStreamsClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class StreamsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawStreamsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawStreamsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawStreamsClient + """ + return self._raw_client + + def create( + self, + call_id: str, + *, + stream_url: str, + stream_type: CallStreamType, + stream_channel: CallStreamChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallStreamResponse: + """ + Starts streaming the media of the call identified by `call_id` to the configured destination. Returns the `stream_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + stream_url : str + WebSocket URL for call streaming + + stream_type : CallStreamType + Direction of audio streamed to `stream_url`. + + stream_channel : CallStreamChannel + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallStreamResponse + Returns the created media stream, including its `stream_id`. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.streams.create( + call_id="call_id", + stream_url="wss://examples.com/stream", + stream_type="oneway", + stream_channel="inbound", + ) + """ + _response = self._raw_client.create( + call_id, + stream_url=stream_url, + stream_type=stream_type, + stream_channel=stream_channel, + request_options=request_options, + ) + return _response.data + + def delete( + self, call_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Stops the media stream identified by `id` on the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + id : str + The `uuid` of the media stream to stop. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The media stream is stopped. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_control.streams.delete( + call_id="call_id", + id="id", + ) + """ + _response = self._raw_client.delete(call_id, id, request_options=request_options) + return _response.data + + +class AsyncStreamsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawStreamsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawStreamsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawStreamsClient + """ + return self._raw_client + + async def create( + self, + call_id: str, + *, + stream_url: str, + stream_type: CallStreamType, + stream_channel: CallStreamChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallStreamResponse: + """ + Starts streaming the media of the call identified by `call_id` to the configured destination. Returns the `stream_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + stream_url : str + WebSocket URL for call streaming + + stream_type : CallStreamType + Direction of audio streamed to `stream_url`. + + stream_channel : CallStreamChannel + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallStreamResponse + Returns the created media stream, including its `stream_id`. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.streams.create( + call_id="call_id", + stream_url="wss://examples.com/stream", + stream_type="oneway", + stream_channel="inbound", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + call_id, + stream_url=stream_url, + stream_type=stream_type, + stream_channel=stream_channel, + request_options=request_options, + ) + return _response.data + + async def delete( + self, call_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Stops the media stream identified by `id` on the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + id : str + The `uuid` of the media stream to stop. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The media stream is stopped. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_control.streams.delete( + call_id="call_id", + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(call_id, id, request_options=request_options) + return _response.data diff --git a/src/wavix/call_control/streams/raw_client.py b/src/wavix/call_control/streams/raw_client.py new file mode 100644 index 0000000..cca3498 --- /dev/null +++ b/src/wavix/call_control/streams/raw_client.py @@ -0,0 +1,325 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.call_stream_channel import CallStreamChannel +from ...types.call_stream_response import CallStreamResponse +from ...types.call_stream_type import CallStreamType +from ...types.success_response import SuccessResponse +from ...types.unauthorized_error_response import UnauthorizedErrorResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawStreamsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + call_id: str, + *, + stream_url: str, + stream_type: CallStreamType, + stream_channel: CallStreamChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CallStreamResponse]: + """ + Starts streaming the media of the call identified by `call_id` to the configured destination. Returns the `stream_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + stream_url : str + WebSocket URL for call streaming + + stream_type : CallStreamType + Direction of audio streamed to `stream_url`. + + stream_channel : CallStreamChannel + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallStreamResponse] + Returns the created media stream, including its `stream_id`. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(call_id)}/streams", + method="POST", + json={ + "stream_url": stream_url, + "stream_type": stream_type, + "stream_channel": stream_channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallStreamResponse, + parse_obj_as( + type_=CallStreamResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, call_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Stops the media stream identified by `id` on the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + id : str + The `uuid` of the media stream to stop. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The media stream is stopped. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(call_id)}/streams/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawStreamsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + call_id: str, + *, + stream_url: str, + stream_type: CallStreamType, + stream_channel: CallStreamChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CallStreamResponse]: + """ + Starts streaming the media of the call identified by `call_id` to the configured destination. Returns the `stream_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + stream_url : str + WebSocket URL for call streaming + + stream_type : CallStreamType + Direction of audio streamed to `stream_url`. + + stream_channel : CallStreamChannel + Audio channel streamed to `stream_url`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallStreamResponse] + Returns the created media stream, including its `stream_id`. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(call_id)}/streams", + method="POST", + json={ + "stream_url": stream_url, + "stream_type": stream_type, + "stream_channel": stream_channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallStreamResponse, + parse_obj_as( + type_=CallStreamResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, call_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Stops the media stream identified by `id` on the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The `uuid` of the call. + + id : str + The `uuid` of the media stream to stop. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The media stream is stopped. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/calls/{encode_path_param(call_id)}/streams/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/call_control/types/__init__.py b/src/wavix/call_control/types/__init__.py new file mode 100644 index 0000000..8b31a6f --- /dev/null +++ b/src/wavix/call_control/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .call_dtmf_collect_request_prompt import CallDtmfCollectRequestPrompt + from .call_dtmf_collect_request_prompt_say import CallDtmfCollectRequestPromptSay +_dynamic_imports: typing.Dict[str, str] = { + "CallDtmfCollectRequestPrompt": ".call_dtmf_collect_request_prompt", + "CallDtmfCollectRequestPromptSay": ".call_dtmf_collect_request_prompt_say", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CallDtmfCollectRequestPrompt", "CallDtmfCollectRequestPromptSay"] diff --git a/src/wavix/call_control/types/call_dtmf_collect_request_prompt.py b/src/wavix/call_control/types/call_dtmf_collect_request_prompt.py new file mode 100644 index 0000000..c9131a2 --- /dev/null +++ b/src/wavix/call_control/types/call_dtmf_collect_request_prompt.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .call_dtmf_collect_request_prompt_say import CallDtmfCollectRequestPromptSay + + +class CallDtmfCollectRequestPrompt(UniversalBaseModel): + """ + Prompt to play before collecting digits. + Play a prerecorded audio file or use Wavix Text-To-Speech. + """ + + play: typing.Optional[str] = pydantic.Field(default=None) + """ + Audio file URL. + """ + + say: typing.Optional[CallDtmfCollectRequestPromptSay] = pydantic.Field(default=None) + """ + Text to speak and voice to use. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/call_control/types/call_dtmf_collect_request_prompt_say.py b/src/wavix/call_control/types/call_dtmf_collect_request_prompt_say.py new file mode 100644 index 0000000..ed353d6 --- /dev/null +++ b/src/wavix/call_control/types/call_dtmf_collect_request_prompt_say.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...types.tts_language import TtsLanguage +from ...types.tts_voice_id import TtsVoiceId + + +class CallDtmfCollectRequestPromptSay(UniversalBaseModel): + """ + Text to speak and voice to use. + """ + + text: str = pydantic.Field() + """ + Text to speak. + """ + + language: typing.Optional[TtsLanguage] = None + voice: TtsVoiceId + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/call_recording/__init__.py b/src/wavix/call_recording/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/call_recording/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/call_recording/client.py b/src/wavix/call_recording/client.py new file mode 100644 index 0000000..0099737 --- /dev/null +++ b/src/wavix/call_recording/client.py @@ -0,0 +1,430 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.call_recording_list_response import CallRecordingListResponse +from ..types.recording import Recording +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawCallRecordingClient, RawCallRecordingClient + + +class CallRecordingClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCallRecordingClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCallRecordingClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCallRecordingClient + """ + return self._raw_client + + def list( + self, + *, + from_date: typing.Optional[dt.date] = None, + to_date: typing.Optional[dt.date] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + call_uuid: typing.Optional[str] = None, + sip_trunks: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallRecordingListResponse: + """ + Returns a paginated list of call recordings for the authenticated account, filtered by date range, number, call, or SIP trunk. + + Parameters + ---------- + from_date : typing.Optional[dt.date] + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : typing.Optional[dt.date] + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + from_ : typing.Optional[str] + Filters recordings by originating phone number. Accepts a full or partial number. + + to : typing.Optional[str] + Filters recordings by destination phone number. Accepts a full or partial number. + + call_uuid : typing.Optional[str] + Filters recordings by the unique call ID. + + sip_trunks : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Filters recordings of outbound calls placed through the listed SIP trunk logins. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallRecordingListResponse + Returns a paginated list of call recordings. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_recording.list( + from_date=datetime.date.fromisoformat( + "2023-01-01", + ), + to_date=datetime.date.fromisoformat( + "2023-12-31", + ), + from_="123456", + to="1987654321", + call_uuid="aa566501-c591-4a8b-b3b9-cc1295398b72", + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + from_date=from_date, + to_date=to_date, + from_=from_, + to=to, + call_uuid=call_uuid, + sip_trunks=sip_trunks, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def get_by_call(self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Redirects to the recording file for the call identified by `call_id`. The download URL is returned in the `Location` header. + + Parameters + ---------- + call_id : str + The unique ID of the call whose recording is retrieved. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_recording.get_by_call( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", + ) + """ + _response = self._raw_client.get_by_call(call_id, request_options=request_options) + return _response.data + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> Recording: + """ + Returns the call recording identified by `id`, including its metadata and download URL. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Recording + Returns the call recording. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_recording.get( + id=123, + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the call recording identified by `id`. Deletion is permanent and removes the recording file. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The recording is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_recording.delete( + id=123, + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncCallRecordingClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCallRecordingClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCallRecordingClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCallRecordingClient + """ + return self._raw_client + + async def list( + self, + *, + from_date: typing.Optional[dt.date] = None, + to_date: typing.Optional[dt.date] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + call_uuid: typing.Optional[str] = None, + sip_trunks: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallRecordingListResponse: + """ + Returns a paginated list of call recordings for the authenticated account, filtered by date range, number, call, or SIP trunk. + + Parameters + ---------- + from_date : typing.Optional[dt.date] + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : typing.Optional[dt.date] + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + from_ : typing.Optional[str] + Filters recordings by originating phone number. Accepts a full or partial number. + + to : typing.Optional[str] + Filters recordings by destination phone number. Accepts a full or partial number. + + call_uuid : typing.Optional[str] + Filters recordings by the unique call ID. + + sip_trunks : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Filters recordings of outbound calls placed through the listed SIP trunk logins. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallRecordingListResponse + Returns a paginated list of call recordings. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_recording.list( + from_date=datetime.date.fromisoformat( + "2023-01-01", + ), + to_date=datetime.date.fromisoformat( + "2023-12-31", + ), + from_="123456", + to="1987654321", + call_uuid="aa566501-c591-4a8b-b3b9-cc1295398b72", + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + from_date=from_date, + to_date=to_date, + from_=from_, + to=to, + call_uuid=call_uuid, + sip_trunks=sip_trunks, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def get_by_call(self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None: + """ + Redirects to the recording file for the call identified by `call_id`. The download URL is returned in the `Location` header. + + Parameters + ---------- + call_id : str + The unique ID of the call whose recording is retrieved. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + None + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_recording.get_by_call( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_by_call(call_id, request_options=request_options) + return _response.data + + async def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> Recording: + """ + Returns the call recording identified by `id`, including its metadata and download URL. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Recording + Returns the call recording. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_recording.get( + id=123, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the call recording identified by `id`. Deletion is permanent and removes the recording file. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The recording is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_recording.delete( + id=123, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/src/wavix/call_recording/raw_client.py b/src/wavix/call_recording/raw_client.py new file mode 100644 index 0000000..3b4c591 --- /dev/null +++ b/src/wavix/call_recording/raw_client.py @@ -0,0 +1,726 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.call_recording_list_response import CallRecordingListResponse +from ..types.recording import Recording +from ..types.success_response import SuccessResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from pydantic import ValidationError + + +class RawCallRecordingClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + from_date: typing.Optional[dt.date] = None, + to_date: typing.Optional[dt.date] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + call_uuid: typing.Optional[str] = None, + sip_trunks: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CallRecordingListResponse]: + """ + Returns a paginated list of call recordings for the authenticated account, filtered by date range, number, call, or SIP trunk. + + Parameters + ---------- + from_date : typing.Optional[dt.date] + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : typing.Optional[dt.date] + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + from_ : typing.Optional[str] + Filters recordings by originating phone number. Accepts a full or partial number. + + to : typing.Optional[str] + Filters recordings by destination phone number. Accepts a full or partial number. + + call_uuid : typing.Optional[str] + Filters recordings by the unique call ID. + + sip_trunks : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Filters recordings of outbound calls placed through the listed SIP trunk logins. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallRecordingListResponse] + Returns a paginated list of call recordings. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/recordings", + method="GET", + params={ + "from_date": str(from_date) if from_date is not None else None, + "to_date": str(to_date) if to_date is not None else None, + "from": from_, + "to": to, + "call_uuid": call_uuid, + "sip_trunks": sip_trunks, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallRecordingListResponse, + parse_obj_as( + type_=CallRecordingListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get_by_call( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[None]: + """ + Redirects to the recording file for the call identified by `call_id`. The download URL is returned in the `Location` header. + + Parameters + ---------- + call_id : str + The unique ID of the call whose recording is retrieved. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[None] + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(call_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Recording]: + """ + Returns the call recording identified by `id`, including its metadata and download URL. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Recording] + Returns the call recording. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Recording, + parse_obj_as( + type_=Recording, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Deletes the call recording identified by `id`. Deletion is permanent and removes the recording file. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The recording is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCallRecordingClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + from_date: typing.Optional[dt.date] = None, + to_date: typing.Optional[dt.date] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + call_uuid: typing.Optional[str] = None, + sip_trunks: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CallRecordingListResponse]: + """ + Returns a paginated list of call recordings for the authenticated account, filtered by date range, number, call, or SIP trunk. + + Parameters + ---------- + from_date : typing.Optional[dt.date] + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : typing.Optional[dt.date] + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + from_ : typing.Optional[str] + Filters recordings by originating phone number. Accepts a full or partial number. + + to : typing.Optional[str] + Filters recordings by destination phone number. Accepts a full or partial number. + + call_uuid : typing.Optional[str] + Filters recordings by the unique call ID. + + sip_trunks : typing.Optional[typing.Union[str, typing.Sequence[str]]] + Filters recordings of outbound calls placed through the listed SIP trunk logins. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallRecordingListResponse] + Returns a paginated list of call recordings. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/recordings", + method="GET", + params={ + "from_date": str(from_date) if from_date is not None else None, + "to_date": str(to_date) if to_date is not None else None, + "from": from_, + "to": to, + "call_uuid": call_uuid, + "sip_trunks": sip_trunks, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallRecordingListResponse, + parse_obj_as( + type_=CallRecordingListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get_by_call( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[None]: + """ + Redirects to the recording file for the call identified by `call_id`. The download URL is returned in the `Location` header. + + Parameters + ---------- + call_id : str + The unique ID of the call whose recording is retrieved. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[None] + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(call_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=None) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Recording]: + """ + Returns the call recording identified by `id`, including its metadata and download URL. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Recording] + Returns the call recording. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Recording, + parse_obj_as( + type_=Recording, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Deletes the call recording identified by `id`. Deletion is permanent and removes the recording file. + + Parameters + ---------- + id : int + The unique ID of the call recording. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The recording is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/recordings/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/call_webhooks/__init__.py b/src/wavix/call_webhooks/__init__.py new file mode 100644 index 0000000..55693c4 --- /dev/null +++ b/src/wavix/call_webhooks/__init__.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CallWebhooksCreateRequestEventType, DeleteCallWebhooksRequestEventType +_dynamic_imports: typing.Dict[str, str] = { + "CallWebhooksCreateRequestEventType": ".types", + "DeleteCallWebhooksRequestEventType": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CallWebhooksCreateRequestEventType", "DeleteCallWebhooksRequestEventType"] diff --git a/src/wavix/call_webhooks/client.py b/src/wavix/call_webhooks/client.py new file mode 100644 index 0000000..7f04bef --- /dev/null +++ b/src/wavix/call_webhooks/client.py @@ -0,0 +1,281 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.call_webhook import CallWebhook +from ..types.call_webhook_list_response import CallWebhookListResponse +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawCallWebhooksClient, RawCallWebhooksClient +from .types.call_webhooks_create_request_event_type import CallWebhooksCreateRequestEventType +from .types.delete_call_webhooks_request_event_type import DeleteCallWebhooksRequestEventType + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CallWebhooksClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCallWebhooksClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCallWebhooksClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCallWebhooksClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> CallWebhookListResponse: + """ + Returns the configured call webhooks for the authenticated account. Wavix sends POST callbacks for `on-call` and `post-call` events. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallWebhookListResponse + Returns the list of call webhooks. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_webhooks.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + url: str, + event_type: CallWebhooksCreateRequestEventType, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallWebhook: + """ + Registers a callback URL for the `on-call` or `post-call` event. Wavix sends a POST callback to the URL when the event occurs. + + Parameters + ---------- + url : str + Webhook URL to send call events to. + + event_type : CallWebhooksCreateRequestEventType + Allowed values: `on-call`, `post-call`. + - `on-call`: Sends real-time status updates + when a call starts, is answered, and ends. + + - `post-call`: Sends a callback after the call ends + with disposition, duration, and cost. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallWebhook + Returns the created call webhook. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_webhooks.create( + url="https://you-site.com/webhook", + event_type="post-call", + ) + """ + _response = self._raw_client.create(url=url, event_type=event_type, request_options=request_options) + return _response.data + + def delete( + self, *, event_type: DeleteCallWebhooksRequestEventType, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Removes the call webhook for the given event type. Wavix stops sending callbacks for that event. + + Parameters + ---------- + event_type : DeleteCallWebhooksRequestEventType + Event type of the webhook to delete. One of `post-call` (callbacks sent after a call ends) or `on-call` (real-time call status callbacks during a call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call webhook is removed. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.call_webhooks.delete( + event_type="post-call", + ) + """ + _response = self._raw_client.delete(event_type=event_type, request_options=request_options) + return _response.data + + +class AsyncCallWebhooksClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCallWebhooksClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCallWebhooksClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCallWebhooksClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> CallWebhookListResponse: + """ + Returns the configured call webhooks for the authenticated account. Wavix sends POST callbacks for `on-call` and `post-call` events. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallWebhookListResponse + Returns the list of call webhooks. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_webhooks.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + url: str, + event_type: CallWebhooksCreateRequestEventType, + request_options: typing.Optional[RequestOptions] = None, + ) -> CallWebhook: + """ + Registers a callback URL for the `on-call` or `post-call` event. Wavix sends a POST callback to the URL when the event occurs. + + Parameters + ---------- + url : str + Webhook URL to send call events to. + + event_type : CallWebhooksCreateRequestEventType + Allowed values: `on-call`, `post-call`. + - `on-call`: Sends real-time status updates + when a call starts, is answered, and ends. + + - `post-call`: Sends a callback after the call ends + with disposition, duration, and cost. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CallWebhook + Returns the created call webhook. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_webhooks.create( + url="https://you-site.com/webhook", + event_type="post-call", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create(url=url, event_type=event_type, request_options=request_options) + return _response.data + + async def delete( + self, *, event_type: DeleteCallWebhooksRequestEventType, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Removes the call webhook for the given event type. Wavix stops sending callbacks for that event. + + Parameters + ---------- + event_type : DeleteCallWebhooksRequestEventType + Event type of the webhook to delete. One of `post-call` (callbacks sent after a call ends) or `on-call` (real-time call status callbacks during a call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The call webhook is removed. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.call_webhooks.delete( + event_type="post-call", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(event_type=event_type, request_options=request_options) + return _response.data diff --git a/src/wavix/call_webhooks/raw_client.py b/src/wavix/call_webhooks/raw_client.py new file mode 100644 index 0000000..6a211a9 --- /dev/null +++ b/src/wavix/call_webhooks/raw_client.py @@ -0,0 +1,529 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.call_webhook import CallWebhook +from ..types.call_webhook_list_response import CallWebhookListResponse +from ..types.success_response import SuccessResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from .types.call_webhooks_create_request_event_type import CallWebhooksCreateRequestEventType +from .types.delete_call_webhooks_request_event_type import DeleteCallWebhooksRequestEventType +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCallWebhooksClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[CallWebhookListResponse]: + """ + Returns the configured call webhooks for the authenticated account. Wavix sends POST callbacks for `on-call` and `post-call` events. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallWebhookListResponse] + Returns the list of call webhooks. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallWebhookListResponse, + parse_obj_as( + type_=CallWebhookListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + url: str, + event_type: CallWebhooksCreateRequestEventType, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CallWebhook]: + """ + Registers a callback URL for the `on-call` or `post-call` event. Wavix sends a POST callback to the URL when the event occurs. + + Parameters + ---------- + url : str + Webhook URL to send call events to. + + event_type : CallWebhooksCreateRequestEventType + Allowed values: `on-call`, `post-call`. + - `on-call`: Sends real-time status updates + when a call starts, is answered, and ends. + + - `post-call`: Sends a callback after the call ends + with disposition, duration, and cost. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CallWebhook] + Returns the created call webhook. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="POST", + json={ + "url": url, + "event_type": event_type, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallWebhook, + parse_obj_as( + type_=CallWebhook, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, *, event_type: DeleteCallWebhooksRequestEventType, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Removes the call webhook for the given event type. Wavix stops sending callbacks for that event. + + Parameters + ---------- + event_type : DeleteCallWebhooksRequestEventType + Event type of the webhook to delete. One of `post-call` (callbacks sent after a call ends) or `on-call` (real-time call status callbacks during a call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The call webhook is removed. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="DELETE", + params={ + "event_type": event_type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCallWebhooksClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CallWebhookListResponse]: + """ + Returns the configured call webhooks for the authenticated account. Wavix sends POST callbacks for `on-call` and `post-call` events. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallWebhookListResponse] + Returns the list of call webhooks. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallWebhookListResponse, + parse_obj_as( + type_=CallWebhookListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + url: str, + event_type: CallWebhooksCreateRequestEventType, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CallWebhook]: + """ + Registers a callback URL for the `on-call` or `post-call` event. Wavix sends a POST callback to the URL when the event occurs. + + Parameters + ---------- + url : str + Webhook URL to send call events to. + + event_type : CallWebhooksCreateRequestEventType + Allowed values: `on-call`, `post-call`. + - `on-call`: Sends real-time status updates + when a call starts, is answered, and ends. + + - `post-call`: Sends a callback after the call ends + with disposition, duration, and cost. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CallWebhook] + Returns the created call webhook. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="POST", + json={ + "url": url, + "event_type": event_type, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CallWebhook, + parse_obj_as( + type_=CallWebhook, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, *, event_type: DeleteCallWebhooksRequestEventType, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Removes the call webhook for the given event type. Wavix stops sending callbacks for that event. + + Parameters + ---------- + event_type : DeleteCallWebhooksRequestEventType + Event type of the webhook to delete. One of `post-call` (callbacks sent after a call ends) or `on-call` (real-time call status callbacks during a call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The call webhook is removed. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/calls/webhooks", + method="DELETE", + params={ + "event_type": event_type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/call_webhooks/types/__init__.py b/src/wavix/call_webhooks/types/__init__.py new file mode 100644 index 0000000..4816626 --- /dev/null +++ b/src/wavix/call_webhooks/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .call_webhooks_create_request_event_type import CallWebhooksCreateRequestEventType + from .delete_call_webhooks_request_event_type import DeleteCallWebhooksRequestEventType +_dynamic_imports: typing.Dict[str, str] = { + "CallWebhooksCreateRequestEventType": ".call_webhooks_create_request_event_type", + "DeleteCallWebhooksRequestEventType": ".delete_call_webhooks_request_event_type", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CallWebhooksCreateRequestEventType", "DeleteCallWebhooksRequestEventType"] diff --git a/src/wavix/call_webhooks/types/call_webhooks_create_request_event_type.py b/src/wavix/call_webhooks/types/call_webhooks_create_request_event_type.py new file mode 100644 index 0000000..1b41429 --- /dev/null +++ b/src/wavix/call_webhooks/types/call_webhooks_create_request_event_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallWebhooksCreateRequestEventType = typing.Union[typing.Literal["post-call", "on-call"], typing.Any] diff --git a/src/wavix/call_webhooks/types/delete_call_webhooks_request_event_type.py b/src/wavix/call_webhooks/types/delete_call_webhooks_request_event_type.py new file mode 100644 index 0000000..d7b7cfb --- /dev/null +++ b/src/wavix/call_webhooks/types/delete_call_webhooks_request_event_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +DeleteCallWebhooksRequestEventType = typing.Union[typing.Literal["post-call", "on-call"], typing.Any] diff --git a/src/wavix/cart/__init__.py b/src/wavix/cart/__init__.py new file mode 100644 index 0000000..eaebc73 --- /dev/null +++ b/src/wavix/cart/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CheckoutCartResponse, GetCartResponse, RemoveCartResponse +_dynamic_imports: typing.Dict[str, str] = { + "CheckoutCartResponse": ".types", + "GetCartResponse": ".types", + "RemoveCartResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CheckoutCartResponse", "GetCartResponse", "RemoveCartResponse"] diff --git a/src/wavix/cart/client.py b/src/wavix/cart/client.py new file mode 100644 index 0000000..d35cdef --- /dev/null +++ b/src/wavix/cart/client.py @@ -0,0 +1,327 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from .raw_client import AsyncRawCartClient, RawCartClient +from .types.checkout_cart_response import CheckoutCartResponse +from .types.get_cart_response import GetCartResponse +from .types.remove_cart_response import RemoveCartResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CartClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCartClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCartClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCartClient + """ + return self._raw_client + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> GetCartResponse: + """ + Returns the current purchase cart, including the phone numbers it contains and the documents each requires. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCartResponse + Returns the cart. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cart.get() + """ + _response = self._raw_client.get(request_options=request_options) + return _response.data + + def add( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[typing.Any]: + """ + Adds the listed phone numbers to the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to add to the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[typing.Any] + Returns the phone numbers now in the cart. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cart.add( + ids=["541139862174", "541139862175"], + ) + """ + _response = self._raw_client.add(ids=ids, request_options=request_options) + return _response.data + + def remove( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> RemoveCartResponse: + """ + Removes the listed phone numbers from the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to remove from the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RemoveCartResponse + Returns a success confirmation. The numbers are removed from the cart. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cart.remove( + ids=["541139862174", "541139862175"], + ) + """ + _response = self._raw_client.remove(ids=ids, request_options=request_options) + return _response.data + + def checkout( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> CheckoutCartResponse: + """ + Purchases the listed phone numbers from the cart. Activation and monthly fees are deducted from the account balance. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers from the cart to purchase. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CheckoutCartResponse + Returns a success confirmation. The phone numbers are purchased. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cart.checkout( + ids=["541139862174", "541139862175"], + ) + """ + _response = self._raw_client.checkout(ids=ids, request_options=request_options) + return _response.data + + +class AsyncCartClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCartClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCartClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCartClient + """ + return self._raw_client + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> GetCartResponse: + """ + Returns the current purchase cart, including the phone numbers it contains and the documents each requires. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCartResponse + Returns the cart. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cart.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_options=request_options) + return _response.data + + async def add( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[typing.Any]: + """ + Adds the listed phone numbers to the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to add to the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[typing.Any] + Returns the phone numbers now in the cart. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cart.add( + ids=["541139862174", "541139862175"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.add(ids=ids, request_options=request_options) + return _response.data + + async def remove( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> RemoveCartResponse: + """ + Removes the listed phone numbers from the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to remove from the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + RemoveCartResponse + Returns a success confirmation. The numbers are removed from the cart. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cart.remove( + ids=["541139862174", "541139862175"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.remove(ids=ids, request_options=request_options) + return _response.data + + async def checkout( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> CheckoutCartResponse: + """ + Purchases the listed phone numbers from the cart. Activation and monthly fees are deducted from the account balance. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers from the cart to purchase. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CheckoutCartResponse + Returns a success confirmation. The phone numbers are purchased. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cart.checkout( + ids=["541139862174", "541139862175"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.checkout(ids=ids, request_options=request_options) + return _response.data diff --git a/src/wavix/cart/raw_client.py b/src/wavix/cart/raw_client.py new file mode 100644 index 0000000..f844a7c --- /dev/null +++ b/src/wavix/cart/raw_client.py @@ -0,0 +1,629 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from .types.checkout_cart_response import CheckoutCartResponse +from .types.get_cart_response import GetCartResponse +from .types.remove_cart_response import RemoveCartResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCartClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[GetCartResponse]: + """ + Returns the current purchase cart, including the phone numbers it contains and the documents each requires. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetCartResponse] + Returns the cart. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCartResponse, + parse_obj_as( + type_=GetCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def add( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[typing.Any]]: + """ + Adds the listed phone numbers to the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to add to the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[typing.Any]] + Returns the phone numbers now in the cart. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="PUT", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[typing.Any], + parse_obj_as( + type_=typing.List[typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def remove( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[RemoveCartResponse]: + """ + Removes the listed phone numbers from the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to remove from the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[RemoveCartResponse] + Returns a success confirmation. The numbers are removed from the cart. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="DELETE", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RemoveCartResponse, + parse_obj_as( + type_=RemoveCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def checkout( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CheckoutCartResponse]: + """ + Purchases the listed phone numbers from the cart. Activation and monthly fees are deducted from the account balance. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers from the cart to purchase. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CheckoutCartResponse] + Returns a success confirmation. The phone numbers are purchased. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/buy/cart/checkout", + method="POST", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CheckoutCartResponse, + parse_obj_as( + type_=CheckoutCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCartClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetCartResponse]: + """ + Returns the current purchase cart, including the phone numbers it contains and the documents each requires. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetCartResponse] + Returns the cart. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCartResponse, + parse_obj_as( + type_=GetCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def add( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[typing.Any]]: + """ + Adds the listed phone numbers to the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to add to the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[typing.Any]] + Returns the phone numbers now in the cart. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="PUT", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[typing.Any], + parse_obj_as( + type_=typing.List[typing.Any], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def remove( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[RemoveCartResponse]: + """ + Removes the listed phone numbers from the purchase cart. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers to remove from the cart. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[RemoveCartResponse] + Returns a success confirmation. The numbers are removed from the cart. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/buy/cart", + method="DELETE", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + RemoveCartResponse, + parse_obj_as( + type_=RemoveCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def checkout( + self, *, ids: typing.Sequence[str], request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CheckoutCartResponse]: + """ + Purchases the listed phone numbers from the cart. Activation and monthly fees are deducted from the account balance. + + Parameters + ---------- + ids : typing.Sequence[str] + Phone numbers from the cart to purchase. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CheckoutCartResponse] + Returns a success confirmation. The phone numbers are purchased. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/buy/cart/checkout", + method="POST", + json={ + "ids": ids, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CheckoutCartResponse, + parse_obj_as( + type_=CheckoutCartResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/cart/types/__init__.py b/src/wavix/cart/types/__init__.py new file mode 100644 index 0000000..f9a9d94 --- /dev/null +++ b/src/wavix/cart/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .checkout_cart_response import CheckoutCartResponse + from .get_cart_response import GetCartResponse + from .remove_cart_response import RemoveCartResponse +_dynamic_imports: typing.Dict[str, str] = { + "CheckoutCartResponse": ".checkout_cart_response", + "GetCartResponse": ".get_cart_response", + "RemoveCartResponse": ".remove_cart_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CheckoutCartResponse", "GetCartResponse", "RemoveCartResponse"] diff --git a/src/wavix/cart/types/checkout_cart_response.py b/src/wavix/cart/types/checkout_cart_response.py new file mode 100644 index 0000000..2a41773 --- /dev/null +++ b/src/wavix/cart/types/checkout_cart_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CheckoutCartResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/cart/types/get_cart_response.py b/src/wavix/cart/types/get_cart_response.py new file mode 100644 index 0000000..619c62b --- /dev/null +++ b/src/wavix/cart/types/get_cart_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...types.available_number import AvailableNumber +from ...types.document_type import DocumentType + + +class GetCartResponse(UniversalBaseModel): + dids: typing.List[AvailableNumber] = pydantic.Field() + """ + List of phone numbers in the cart. + """ + + doc_types: typing.List[DocumentType] = pydantic.Field() + """ + Document types required to activate phone numbers. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/cart/types/remove_cart_response.py b/src/wavix/cart/types/remove_cart_response.py new file mode 100644 index 0000000..87cc0de --- /dev/null +++ b/src/wavix/cart/types/remove_cart_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RemoveCartResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/cdrs/__init__.py b/src/wavix/cdrs/__init__.py new file mode 100644 index 0000000..adfd295 --- /dev/null +++ b/src/wavix/cdrs/__init__.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CdrSearchRequestDisposition, CdrSearchRequestType + from . import transcription +_dynamic_imports: typing.Dict[str, str] = { + "CdrSearchRequestDisposition": ".types", + "CdrSearchRequestType": ".types", + "transcription": ".transcription", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CdrSearchRequestDisposition", "CdrSearchRequestType", "transcription"] diff --git a/src/wavix/cdrs/client.py b/src/wavix/cdrs/client.py new file mode 100644 index 0000000..77175d0 --- /dev/null +++ b/src/wavix/cdrs/client.py @@ -0,0 +1,968 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.call_disposition import CallDisposition +from ..types.cdr_list_response import CdrListResponse +from ..types.cdr_response import CdrResponse +from ..types.cdr_transcription_response import CdrTranscriptionResponse +from ..types.cdr_transcription_search_response import CdrTranscriptionSearchResponse +from ..types.success_response import SuccessResponse +from ..types.transcription_filter import TranscriptionFilter +from ..types.transcription_language import TranscriptionLanguage +from .raw_client import AsyncRawCdrsClient, RawCdrsClient +from .types.cdr_search_request_disposition import CdrSearchRequestDisposition +from .types.cdr_search_request_type import CdrSearchRequestType + +if typing.TYPE_CHECKING: + from .transcription.client import AsyncTranscriptionClient, TranscriptionClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CdrsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCdrsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transcription: typing.Optional[TranscriptionClient] = None + + @property + def with_raw_response(self) -> RawCdrsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCdrsClient + """ + return self._raw_client + + def list( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrListResponse: + """ + Returns a paginated list of call detail records for the authenticated account, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrListResponse + Returns a paginated list of CDRs. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.list( + from_=datetime.date.fromisoformat( + "2023-01-01", + ), + to=datetime.date.fromisoformat( + "2023-09-01", + ), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + from_=from_, + to=to, + type=type, + disposition=disposition, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + uuid_=uuid_, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def search( + self, + *, + type: CdrSearchRequestType, + from_: dt.date, + to: dt.date, + page: int, + per_page: int, + from_search: typing.Optional[str] = OMIT, + to_search: typing.Optional[str] = OMIT, + sip_trunk: typing.Optional[str] = OMIT, + min_duration: typing.Optional[int] = OMIT, + transcription: typing.Optional[TranscriptionFilter] = OMIT, + uuid_: typing.Optional[str] = OMIT, + disposition: typing.Optional[CdrSearchRequestDisposition] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrTranscriptionSearchResponse: + """ + Searches call transcriptions for the given keywords or phrases and returns the matching CDRs with their transcriptions. + + Parameters + ---------- + type : CdrSearchRequestType + Filters by call type. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + from_ : dt.date + Start date for call search in `YYYY-MM-DD` format. + + to : dt.date + End date for call search in `YYYY-MM-DD` format. + + page : int + Page number to retrieve. + + per_page : int + Number of records per page. + + from_search : typing.Optional[str] + Originating phone number to filter results. Accepts full or partial number. + + to_search : typing.Optional[str] + Destination phone number to filter results. Accepts full or partial number. + + sip_trunk : typing.Optional[str] + SIP trunk login to filter outbound calls. Ignored for inbound calls. + + min_duration : typing.Optional[int] + Minimum call duration in seconds. + + transcription : typing.Optional[TranscriptionFilter] + + uuid_ : typing.Optional[str] + Call ID. + + disposition : typing.Optional[CdrSearchRequestDisposition] + Call disposition to filter results. If omitted, returns only answered + calls. Allowed values: `answered`, `busy`, `rejected`, + `failed`, `all`. Use `all` to return calls + regardless of their disposition. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionSearchResponse + Returns a paginated list of matching CDRs with their transcriptions. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.search( + type="placed", + from_=datetime.date.fromisoformat( + "2023-08-01", + ), + to=datetime.date.fromisoformat( + "2023-08-31", + ), + page=1, + per_page=50, + ) + """ + _response = self._raw_client.search( + type=type, + from_=from_, + to=to, + page=page, + per_page=per_page, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + min_duration=min_duration, + transcription=transcription, + uuid_=uuid_, + disposition=disposition, + request_options=request_options, + ) + return _response.data + + def retranscribe( + self, + call_id: str, + *, + language: typing.Optional[TranscriptionLanguage] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Transcribes the recording of the call identified by `call_id`. Transcription is asynchronous; poll the transcription endpoint for the result. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + language : typing.Optional[TranscriptionLanguage] + + webhook_url : typing.Optional[str] + Webhook URL to receive status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Transcription is queued. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.retranscribe( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + """ + _response = self._raw_client.retranscribe( + call_id, language=language, webhook_url=webhook_url, request_options=request_options + ) + return _response.data + + def transcriptions( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CdrTranscriptionResponse: + """ + Returns the transcription of the recorded call identified by `call_id`. Alias of the `transcription` endpoint. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionResponse + Returns the call transcription. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.transcriptions( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + """ + _response = self._raw_client.transcriptions(call_id, request_options=request_options) + return _response.data + + def get( + self, + call_id: str, + *, + show_transcription: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrResponse: + """ + Returns the call detail record for the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + show_transcription : typing.Optional[bool] + When `true`, includes the call transcription in the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrResponse + Returns the CDR. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.get( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", + show_transcription=True, + ) + """ + _response = self._raw_client.get( + call_id, show_transcription=show_transcription, request_options=request_options + ) + return _response.data + + def list_all( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> str: + """ + Streams matching call detail records as newline-delimited JSON (NDJSON), one record per line, for bulk export. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + Returns an NDJSON stream of CDRs, one record per line. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.list_all( + from_=datetime.date.fromisoformat( + "2023-01-01", + ), + to=datetime.date.fromisoformat( + "2023-09-01", + ), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list_all( + from_=from_, + to=to, + type=type, + disposition=disposition, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + uuid_=uuid_, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + @property + def transcription(self): + if self._transcription is None: + from .transcription.client import TranscriptionClient # noqa: E402 + + self._transcription = TranscriptionClient(client_wrapper=self._client_wrapper) + return self._transcription + + +class AsyncCdrsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCdrsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transcription: typing.Optional[AsyncTranscriptionClient] = None + + @property + def with_raw_response(self) -> AsyncRawCdrsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCdrsClient + """ + return self._raw_client + + async def list( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrListResponse: + """ + Returns a paginated list of call detail records for the authenticated account, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrListResponse + Returns a paginated list of CDRs. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.list( + from_=datetime.date.fromisoformat( + "2023-01-01", + ), + to=datetime.date.fromisoformat( + "2023-09-01", + ), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + from_=from_, + to=to, + type=type, + disposition=disposition, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + uuid_=uuid_, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def search( + self, + *, + type: CdrSearchRequestType, + from_: dt.date, + to: dt.date, + page: int, + per_page: int, + from_search: typing.Optional[str] = OMIT, + to_search: typing.Optional[str] = OMIT, + sip_trunk: typing.Optional[str] = OMIT, + min_duration: typing.Optional[int] = OMIT, + transcription: typing.Optional[TranscriptionFilter] = OMIT, + uuid_: typing.Optional[str] = OMIT, + disposition: typing.Optional[CdrSearchRequestDisposition] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrTranscriptionSearchResponse: + """ + Searches call transcriptions for the given keywords or phrases and returns the matching CDRs with their transcriptions. + + Parameters + ---------- + type : CdrSearchRequestType + Filters by call type. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + from_ : dt.date + Start date for call search in `YYYY-MM-DD` format. + + to : dt.date + End date for call search in `YYYY-MM-DD` format. + + page : int + Page number to retrieve. + + per_page : int + Number of records per page. + + from_search : typing.Optional[str] + Originating phone number to filter results. Accepts full or partial number. + + to_search : typing.Optional[str] + Destination phone number to filter results. Accepts full or partial number. + + sip_trunk : typing.Optional[str] + SIP trunk login to filter outbound calls. Ignored for inbound calls. + + min_duration : typing.Optional[int] + Minimum call duration in seconds. + + transcription : typing.Optional[TranscriptionFilter] + + uuid_ : typing.Optional[str] + Call ID. + + disposition : typing.Optional[CdrSearchRequestDisposition] + Call disposition to filter results. If omitted, returns only answered + calls. Allowed values: `answered`, `busy`, `rejected`, + `failed`, `all`. Use `all` to return calls + regardless of their disposition. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionSearchResponse + Returns a paginated list of matching CDRs with their transcriptions. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.search( + type="placed", + from_=datetime.date.fromisoformat( + "2023-08-01", + ), + to=datetime.date.fromisoformat( + "2023-08-31", + ), + page=1, + per_page=50, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.search( + type=type, + from_=from_, + to=to, + page=page, + per_page=per_page, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + min_duration=min_duration, + transcription=transcription, + uuid_=uuid_, + disposition=disposition, + request_options=request_options, + ) + return _response.data + + async def retranscribe( + self, + call_id: str, + *, + language: typing.Optional[TranscriptionLanguage] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Transcribes the recording of the call identified by `call_id`. Transcription is asynchronous; poll the transcription endpoint for the result. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + language : typing.Optional[TranscriptionLanguage] + + webhook_url : typing.Optional[str] + Webhook URL to receive status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Transcription is queued. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.retranscribe( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.retranscribe( + call_id, language=language, webhook_url=webhook_url, request_options=request_options + ) + return _response.data + + async def transcriptions( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CdrTranscriptionResponse: + """ + Returns the transcription of the recorded call identified by `call_id`. Alias of the `transcription` endpoint. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionResponse + Returns the call transcription. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.transcriptions( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.transcriptions(call_id, request_options=request_options) + return _response.data + + async def get( + self, + call_id: str, + *, + show_transcription: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> CdrResponse: + """ + Returns the call detail record for the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + show_transcription : typing.Optional[bool] + When `true`, includes the call transcription in the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrResponse + Returns the CDR. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.get( + call_id="aa566501-c591-4a8b-b3b9-cc1295398b72", + show_transcription=True, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get( + call_id, show_transcription=show_transcription, request_options=request_options + ) + return _response.data + + async def list_all( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> str: + """ + Streams matching call detail records as newline-delimited JSON (NDJSON), one record per line, for bulk export. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + Returns an NDJSON stream of CDRs, one record per line. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.list_all( + from_=datetime.date.fromisoformat( + "2023-01-01", + ), + to=datetime.date.fromisoformat( + "2023-09-01", + ), + type="received", + from_search="13524815863", + to_search="12565378257", + sip_trunk="12321", + uuid_="99df5ffd-962a-410f-bcce-d08f1f7f328c", + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_all( + from_=from_, + to=to, + type=type, + disposition=disposition, + from_search=from_search, + to_search=to_search, + sip_trunk=sip_trunk, + uuid_=uuid_, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + @property + def transcription(self): + if self._transcription is None: + from .transcription.client import AsyncTranscriptionClient # noqa: E402 + + self._transcription = AsyncTranscriptionClient(client_wrapper=self._client_wrapper) + return self._transcription diff --git a/src/wavix/cdrs/raw_client.py b/src/wavix/cdrs/raw_client.py new file mode 100644 index 0000000..bbbec06 --- /dev/null +++ b/src/wavix/cdrs/raw_client.py @@ -0,0 +1,1176 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.call_disposition import CallDisposition +from ..types.cdr_list_response import CdrListResponse +from ..types.cdr_response import CdrResponse +from ..types.cdr_transcription_response import CdrTranscriptionResponse +from ..types.cdr_transcription_search_response import CdrTranscriptionSearchResponse +from ..types.success_response import SuccessResponse +from ..types.transcription_filter import TranscriptionFilter +from ..types.transcription_language import TranscriptionLanguage +from .types.cdr_search_request_disposition import CdrSearchRequestDisposition +from .types.cdr_search_request_type import CdrSearchRequestType +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCdrsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CdrListResponse]: + """ + Returns a paginated list of call detail records for the authenticated account, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CdrListResponse] + Returns a paginated list of CDRs. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/cdrs", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "type": type, + "disposition": disposition, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "uuid": uuid_, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrListResponse, + parse_obj_as( + type_=CdrListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def search( + self, + *, + type: CdrSearchRequestType, + from_: dt.date, + to: dt.date, + page: int, + per_page: int, + from_search: typing.Optional[str] = OMIT, + to_search: typing.Optional[str] = OMIT, + sip_trunk: typing.Optional[str] = OMIT, + min_duration: typing.Optional[int] = OMIT, + transcription: typing.Optional[TranscriptionFilter] = OMIT, + uuid_: typing.Optional[str] = OMIT, + disposition: typing.Optional[CdrSearchRequestDisposition] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CdrTranscriptionSearchResponse]: + """ + Searches call transcriptions for the given keywords or phrases and returns the matching CDRs with their transcriptions. + + Parameters + ---------- + type : CdrSearchRequestType + Filters by call type. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + from_ : dt.date + Start date for call search in `YYYY-MM-DD` format. + + to : dt.date + End date for call search in `YYYY-MM-DD` format. + + page : int + Page number to retrieve. + + per_page : int + Number of records per page. + + from_search : typing.Optional[str] + Originating phone number to filter results. Accepts full or partial number. + + to_search : typing.Optional[str] + Destination phone number to filter results. Accepts full or partial number. + + sip_trunk : typing.Optional[str] + SIP trunk login to filter outbound calls. Ignored for inbound calls. + + min_duration : typing.Optional[int] + Minimum call duration in seconds. + + transcription : typing.Optional[TranscriptionFilter] + + uuid_ : typing.Optional[str] + Call ID. + + disposition : typing.Optional[CdrSearchRequestDisposition] + Call disposition to filter results. If omitted, returns only answered + calls. Allowed values: `answered`, `busy`, `rejected`, + `failed`, `all`. Use `all` to return calls + regardless of their disposition. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CdrTranscriptionSearchResponse] + Returns a paginated list of matching CDRs with their transcriptions. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/cdrs", + method="POST", + json={ + "type": type, + "from": from_, + "to": to, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "min_duration": min_duration, + "transcription": convert_and_respect_annotation_metadata( + object_=transcription, annotation=TranscriptionFilter, direction="write" + ), + "uuid": uuid_, + "disposition": disposition, + "page": page, + "per_page": per_page, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionSearchResponse, + parse_obj_as( + type_=CdrTranscriptionSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def retranscribe( + self, + call_id: str, + *, + language: typing.Optional[TranscriptionLanguage] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SuccessResponse]: + """ + Transcribes the recording of the call identified by `call_id`. Transcription is asynchronous; poll the transcription endpoint for the result. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + language : typing.Optional[TranscriptionLanguage] + + webhook_url : typing.Optional[str] + Webhook URL to receive status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. Transcription is queued. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/retranscribe", + method="PUT", + json={ + "language": language, + "webhook_url": webhook_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def transcriptions( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CdrTranscriptionResponse]: + """ + Returns the transcription of the recorded call identified by `call_id`. Alias of the `transcription` endpoint. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CdrTranscriptionResponse] + Returns the call transcription. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/transcriptions", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionResponse, + parse_obj_as( + type_=CdrTranscriptionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, + call_id: str, + *, + show_transcription: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CdrResponse]: + """ + Returns the call detail record for the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + show_transcription : typing.Optional[bool] + When `true`, includes the call transcription in the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CdrResponse] + Returns the CDR. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}", + method="GET", + params={ + "show_transcription": show_transcription, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrResponse, + parse_obj_as( + type_=CdrResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_all( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[str]: + """ + Streams matching call detail records as newline-delimited JSON (NDJSON), one record per line, for bulk export. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[str] + Returns an NDJSON stream of CDRs, one record per line. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/cdrs/all", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "type": type, + "disposition": disposition, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "uuid": uuid_, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + str, + parse_obj_as( + type_=str, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCdrsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CdrListResponse]: + """ + Returns a paginated list of call detail records for the authenticated account, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CdrListResponse] + Returns a paginated list of CDRs. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/cdrs", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "type": type, + "disposition": disposition, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "uuid": uuid_, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrListResponse, + parse_obj_as( + type_=CdrListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def search( + self, + *, + type: CdrSearchRequestType, + from_: dt.date, + to: dt.date, + page: int, + per_page: int, + from_search: typing.Optional[str] = OMIT, + to_search: typing.Optional[str] = OMIT, + sip_trunk: typing.Optional[str] = OMIT, + min_duration: typing.Optional[int] = OMIT, + transcription: typing.Optional[TranscriptionFilter] = OMIT, + uuid_: typing.Optional[str] = OMIT, + disposition: typing.Optional[CdrSearchRequestDisposition] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CdrTranscriptionSearchResponse]: + """ + Searches call transcriptions for the given keywords or phrases and returns the matching CDRs with their transcriptions. + + Parameters + ---------- + type : CdrSearchRequestType + Filters by call type. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + from_ : dt.date + Start date for call search in `YYYY-MM-DD` format. + + to : dt.date + End date for call search in `YYYY-MM-DD` format. + + page : int + Page number to retrieve. + + per_page : int + Number of records per page. + + from_search : typing.Optional[str] + Originating phone number to filter results. Accepts full or partial number. + + to_search : typing.Optional[str] + Destination phone number to filter results. Accepts full or partial number. + + sip_trunk : typing.Optional[str] + SIP trunk login to filter outbound calls. Ignored for inbound calls. + + min_duration : typing.Optional[int] + Minimum call duration in seconds. + + transcription : typing.Optional[TranscriptionFilter] + + uuid_ : typing.Optional[str] + Call ID. + + disposition : typing.Optional[CdrSearchRequestDisposition] + Call disposition to filter results. If omitted, returns only answered + calls. Allowed values: `answered`, `busy`, `rejected`, + `failed`, `all`. Use `all` to return calls + regardless of their disposition. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CdrTranscriptionSearchResponse] + Returns a paginated list of matching CDRs with their transcriptions. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/cdrs", + method="POST", + json={ + "type": type, + "from": from_, + "to": to, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "min_duration": min_duration, + "transcription": convert_and_respect_annotation_metadata( + object_=transcription, annotation=TranscriptionFilter, direction="write" + ), + "uuid": uuid_, + "disposition": disposition, + "page": page, + "per_page": per_page, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionSearchResponse, + parse_obj_as( + type_=CdrTranscriptionSearchResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def retranscribe( + self, + call_id: str, + *, + language: typing.Optional[TranscriptionLanguage] = OMIT, + webhook_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Transcribes the recording of the call identified by `call_id`. Transcription is asynchronous; poll the transcription endpoint for the result. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + language : typing.Optional[TranscriptionLanguage] + + webhook_url : typing.Optional[str] + Webhook URL to receive status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. Transcription is queued. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/retranscribe", + method="PUT", + json={ + "language": language, + "webhook_url": webhook_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def transcriptions( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CdrTranscriptionResponse]: + """ + Returns the transcription of the recorded call identified by `call_id`. Alias of the `transcription` endpoint. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CdrTranscriptionResponse] + Returns the call transcription. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/transcriptions", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionResponse, + parse_obj_as( + type_=CdrTranscriptionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, + call_id: str, + *, + show_transcription: typing.Optional[bool] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CdrResponse]: + """ + Returns the call detail record for the call identified by `call_id`. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + show_transcription : typing.Optional[bool] + When `true`, includes the call transcription in the response. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CdrResponse] + Returns the CDR. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}", + method="GET", + params={ + "show_transcription": show_transcription, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrResponse, + parse_obj_as( + type_=CdrResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_all( + self, + *, + from_: dt.date, + to: dt.date, + type: str, + disposition: typing.Optional[CallDisposition] = None, + from_search: typing.Optional[str] = None, + to_search: typing.Optional[str] = None, + sip_trunk: typing.Optional[str] = None, + uuid_: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[str]: + """ + Streams matching call detail records as newline-delimited JSON (NDJSON), one record per line, for bulk export. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : str + Filters CDRs by call direction. One of `placed` (outbound calls dialed by the account) or `received` (inbound calls answered by the account). + + disposition : typing.Optional[CallDisposition] + Filters CDRs by call disposition. One of `answered` (the called party answered), `busy` (the called party was busy), `rejected` (the call was declined), `failed` (the call could not be routed), or `all` (no disposition filter). + + from_search : typing.Optional[str] + Filters CDRs by originating phone number. Accepts a full or partial number. + + to_search : typing.Optional[str] + Filters CDRs by destination phone number. Accepts a full or partial number. + + sip_trunk : typing.Optional[str] + Filters outbound CDRs by SIP trunk login. Ignored for inbound calls. + + uuid_ : typing.Optional[str] + Filters CDRs by the unique call ID. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[str] + Returns an NDJSON stream of CDRs, one record per line. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/cdrs/all", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "type": type, + "disposition": disposition, + "from_search": from_search, + "to_search": to_search, + "sip_trunk": sip_trunk, + "uuid": uuid_, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + str, + parse_obj_as( + type_=str, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/cdrs/transcription/__init__.py b/src/wavix/cdrs/transcription/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/cdrs/transcription/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/cdrs/transcription/client.py b/src/wavix/cdrs/transcription/client.py new file mode 100644 index 0000000..36f5295 --- /dev/null +++ b/src/wavix/cdrs/transcription/client.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.cdr_transcription_response import CdrTranscriptionResponse +from .raw_client import AsyncRawTranscriptionClient, RawTranscriptionClient + + +class TranscriptionClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTranscriptionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTranscriptionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTranscriptionClient + """ + return self._raw_client + + def get(self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> CdrTranscriptionResponse: + """ + Returns the transcription of the recorded call identified by `call_id`, including the transcript, speaker turns, and summary. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionResponse + Returns the call transcription. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.cdrs.transcription.get( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + """ + _response = self._raw_client.get(call_id, request_options=request_options) + return _response.data + + +class AsyncTranscriptionClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTranscriptionClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTranscriptionClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTranscriptionClient + """ + return self._raw_client + + async def get( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> CdrTranscriptionResponse: + """ + Returns the transcription of the recorded call identified by `call_id`, including the transcript, speaker turns, and summary. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CdrTranscriptionResponse + Returns the call transcription. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.cdrs.transcription.get( + call_id="bbaa37bf-430a-46da-ade3-c248e407016", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(call_id, request_options=request_options) + return _response.data diff --git a/src/wavix/cdrs/transcription/raw_client.py b/src/wavix/cdrs/transcription/raw_client.py new file mode 100644 index 0000000..c11e05c --- /dev/null +++ b/src/wavix/cdrs/transcription/raw_client.py @@ -0,0 +1,156 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.cdr_transcription_response import CdrTranscriptionResponse +from pydantic import ValidationError + + +class RawTranscriptionClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CdrTranscriptionResponse]: + """ + Returns the transcription of the recorded call identified by `call_id`, including the transcript, speaker turns, and summary. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CdrTranscriptionResponse] + Returns the call transcription. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/transcription", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionResponse, + parse_obj_as( + type_=CdrTranscriptionResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawTranscriptionClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, call_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CdrTranscriptionResponse]: + """ + Returns the transcription of the recorded call identified by `call_id`, including the transcript, speaker turns, and summary. + + Parameters + ---------- + call_id : str + The unique ID of the call. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CdrTranscriptionResponse] + Returns the call transcription. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/cdrs/{encode_path_param(call_id)}/transcription", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CdrTranscriptionResponse, + parse_obj_as( + type_=CdrTranscriptionResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/cdrs/types/__init__.py b/src/wavix/cdrs/types/__init__.py new file mode 100644 index 0000000..e2ad5ff --- /dev/null +++ b/src/wavix/cdrs/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .cdr_search_request_disposition import CdrSearchRequestDisposition + from .cdr_search_request_type import CdrSearchRequestType +_dynamic_imports: typing.Dict[str, str] = { + "CdrSearchRequestDisposition": ".cdr_search_request_disposition", + "CdrSearchRequestType": ".cdr_search_request_type", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CdrSearchRequestDisposition", "CdrSearchRequestType"] diff --git a/src/wavix/cdrs/types/cdr_search_request_disposition.py b/src/wavix/cdrs/types/cdr_search_request_disposition.py new file mode 100644 index 0000000..d82988a --- /dev/null +++ b/src/wavix/cdrs/types/cdr_search_request_disposition.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CdrSearchRequestDisposition = typing.Union[typing.Literal["answered", "noanswer", "busy", "failed", "all"], typing.Any] diff --git a/src/wavix/cdrs/types/cdr_search_request_type.py b/src/wavix/cdrs/types/cdr_search_request_type.py new file mode 100644 index 0000000..95669cd --- /dev/null +++ b/src/wavix/cdrs/types/cdr_search_request_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CdrSearchRequestType = typing.Union[typing.Literal["placed", "received"], typing.Any] diff --git a/src/wavix/client.py b/src/wavix/client.py new file mode 100644 index 0000000..528ec77 --- /dev/null +++ b/src/wavix/client.py @@ -0,0 +1,578 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import httpx +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .core.logging import LogConfig, Logger +from .environment import WavixEnvironment + +if typing.TYPE_CHECKING: + from .api_keys.client import ApiKeysClient, AsyncApiKeysClient + from .billing.client import AsyncBillingClient, BillingClient + from .buy.client import AsyncBuyClient, BuyClient + from .call_control.client import AsyncCallControlClient, CallControlClient + from .call_recording.client import AsyncCallRecordingClient, CallRecordingClient + from .call_webhooks.client import AsyncCallWebhooksClient, CallWebhooksClient + from .cart.client import AsyncCartClient, CartClient + from .cdrs.client import AsyncCdrsClient, CdrsClient + from .link_shortener.client import AsyncLinkShortenerClient, LinkShortenerClient + from .number_validator.client import AsyncNumberValidatorClient, NumberValidatorClient + from .numbers.client import AsyncNumbersClient, NumbersClient + from .profile.client import AsyncProfileClient, ProfileClient + from .sip_trunks.client import AsyncSipTrunksClient, SipTrunksClient + from .sms_and_mms.client import AsyncSmsAndMmsClient, SmsAndMmsClient + from .speech_analytics.client import AsyncSpeechAnalyticsClient, SpeechAnalyticsClient + from .sub_accounts.client import AsyncSubAccountsClient, SubAccountsClient + from .ten_dlc.client import AsyncTenDlcClient, TenDlcClient + from .two_fa.client import AsyncTwoFaClient, TwoFaClient + from .voice_campaigns.client import AsyncVoiceCampaignsClient, VoiceCampaignsClient + from .webrtc.client import AsyncWebrtcClient, WebrtcClient + + +class Wavix: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : WavixEnvironment + The environment to use for requests from the client. from .environment import WavixEnvironment + + + + Defaults to WavixEnvironment.DEFAULT + + + + token : typing.Union[str, typing.Callable[[], str]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + max_retries : typing.Optional[int] + The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: WavixEnvironment = WavixEnvironment.DEFAULT, + token: typing.Union[str, typing.Callable[[], str]], + headers: typing.Optional[typing.Dict[str, str]] = None, + timeout: typing.Optional[float] = None, + max_retries: typing.Optional[int] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + _defaulted_max_retries = max_retries if max_retries is not None else 2 + self._client_wrapper = SyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + token=token, + headers=headers, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + max_retries=_defaulted_max_retries, + logging=logging, + ) + self._api_keys: typing.Optional[ApiKeysClient] = None + self._sip_trunks: typing.Optional[SipTrunksClient] = None + self._cart: typing.Optional[CartClient] = None + self._numbers: typing.Optional[NumbersClient] = None + self._cdrs: typing.Optional[CdrsClient] = None + self._call_recording: typing.Optional[CallRecordingClient] = None + self._speech_analytics: typing.Optional[SpeechAnalyticsClient] = None + self._call_webhooks: typing.Optional[CallWebhooksClient] = None + self._call_control: typing.Optional[CallControlClient] = None + self._number_validator: typing.Optional[NumberValidatorClient] = None + self._voice_campaigns: typing.Optional[VoiceCampaignsClient] = None + self._link_shortener: typing.Optional[LinkShortenerClient] = None + self._profile: typing.Optional[ProfileClient] = None + self._sub_accounts: typing.Optional[SubAccountsClient] = None + self._sms_and_mms: typing.Optional[SmsAndMmsClient] = None + self._billing: typing.Optional[BillingClient] = None + self._buy: typing.Optional[BuyClient] = None + self._ten_dlc: typing.Optional[TenDlcClient] = None + self._two_fa: typing.Optional[TwoFaClient] = None + self._webrtc: typing.Optional[WebrtcClient] = None + + @property + def api_keys(self): + if self._api_keys is None: + from .api_keys.client import ApiKeysClient # noqa: E402 + + self._api_keys = ApiKeysClient(client_wrapper=self._client_wrapper) + return self._api_keys + + @property + def sip_trunks(self): + if self._sip_trunks is None: + from .sip_trunks.client import SipTrunksClient # noqa: E402 + + self._sip_trunks = SipTrunksClient(client_wrapper=self._client_wrapper) + return self._sip_trunks + + @property + def cart(self): + if self._cart is None: + from .cart.client import CartClient # noqa: E402 + + self._cart = CartClient(client_wrapper=self._client_wrapper) + return self._cart + + @property + def numbers(self): + if self._numbers is None: + from .numbers.client import NumbersClient # noqa: E402 + + self._numbers = NumbersClient(client_wrapper=self._client_wrapper) + return self._numbers + + @property + def cdrs(self): + if self._cdrs is None: + from .cdrs.client import CdrsClient # noqa: E402 + + self._cdrs = CdrsClient(client_wrapper=self._client_wrapper) + return self._cdrs + + @property + def call_recording(self): + if self._call_recording is None: + from .call_recording.client import CallRecordingClient # noqa: E402 + + self._call_recording = CallRecordingClient(client_wrapper=self._client_wrapper) + return self._call_recording + + @property + def speech_analytics(self): + if self._speech_analytics is None: + from .speech_analytics.client import SpeechAnalyticsClient # noqa: E402 + + self._speech_analytics = SpeechAnalyticsClient(client_wrapper=self._client_wrapper) + return self._speech_analytics + + @property + def call_webhooks(self): + if self._call_webhooks is None: + from .call_webhooks.client import CallWebhooksClient # noqa: E402 + + self._call_webhooks = CallWebhooksClient(client_wrapper=self._client_wrapper) + return self._call_webhooks + + @property + def call_control(self): + if self._call_control is None: + from .call_control.client import CallControlClient # noqa: E402 + + self._call_control = CallControlClient(client_wrapper=self._client_wrapper) + return self._call_control + + @property + def number_validator(self): + if self._number_validator is None: + from .number_validator.client import NumberValidatorClient # noqa: E402 + + self._number_validator = NumberValidatorClient(client_wrapper=self._client_wrapper) + return self._number_validator + + @property + def voice_campaigns(self): + if self._voice_campaigns is None: + from .voice_campaigns.client import VoiceCampaignsClient # noqa: E402 + + self._voice_campaigns = VoiceCampaignsClient(client_wrapper=self._client_wrapper) + return self._voice_campaigns + + @property + def link_shortener(self): + if self._link_shortener is None: + from .link_shortener.client import LinkShortenerClient # noqa: E402 + + self._link_shortener = LinkShortenerClient(client_wrapper=self._client_wrapper) + return self._link_shortener + + @property + def profile(self): + if self._profile is None: + from .profile.client import ProfileClient # noqa: E402 + + self._profile = ProfileClient(client_wrapper=self._client_wrapper) + return self._profile + + @property + def sub_accounts(self): + if self._sub_accounts is None: + from .sub_accounts.client import SubAccountsClient # noqa: E402 + + self._sub_accounts = SubAccountsClient(client_wrapper=self._client_wrapper) + return self._sub_accounts + + @property + def sms_and_mms(self): + if self._sms_and_mms is None: + from .sms_and_mms.client import SmsAndMmsClient # noqa: E402 + + self._sms_and_mms = SmsAndMmsClient(client_wrapper=self._client_wrapper) + return self._sms_and_mms + + @property + def billing(self): + if self._billing is None: + from .billing.client import BillingClient # noqa: E402 + + self._billing = BillingClient(client_wrapper=self._client_wrapper) + return self._billing + + @property + def buy(self): + if self._buy is None: + from .buy.client import BuyClient # noqa: E402 + + self._buy = BuyClient(client_wrapper=self._client_wrapper) + return self._buy + + @property + def ten_dlc(self): + if self._ten_dlc is None: + from .ten_dlc.client import TenDlcClient # noqa: E402 + + self._ten_dlc = TenDlcClient(client_wrapper=self._client_wrapper) + return self._ten_dlc + + @property + def two_fa(self): + if self._two_fa is None: + from .two_fa.client import TwoFaClient # noqa: E402 + + self._two_fa = TwoFaClient(client_wrapper=self._client_wrapper) + return self._two_fa + + @property + def webrtc(self): + if self._webrtc is None: + from .webrtc.client import WebrtcClient # noqa: E402 + + self._webrtc = WebrtcClient(client_wrapper=self._client_wrapper) + return self._webrtc + + +def _make_default_async_client( + timeout: typing.Optional[float], + follow_redirects: typing.Optional[bool], +) -> httpx.AsyncClient: + try: + import httpx_aiohttp # type: ignore[import-not-found] + except ImportError: + pass + else: + if follow_redirects is not None: + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx_aiohttp.HttpxAiohttpClient(timeout=timeout) + + if follow_redirects is not None: + return httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) + return httpx.AsyncClient(timeout=timeout) + + +class AsyncWavix: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : typing.Optional[str] + The base url to use for requests from the client. + + environment : WavixEnvironment + The environment to use for requests from the client. from .environment import WavixEnvironment + + + + Defaults to WavixEnvironment.DEFAULT + + + + token : typing.Union[str, typing.Callable[[], str]] + headers : typing.Optional[typing.Dict[str, str]] + Additional headers to send with every request. + + async_token : typing.Optional[typing.Callable[[], typing.Awaitable[str]]] + An async callable that returns a bearer token. Use this when token acquisition involves async I/O (e.g., refreshing tokens via an async HTTP client). When provided, this is used instead of the synchronous token for async requests. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + max_retries : typing.Optional[int] + The default maximum number of retries for failed requests. Defaults to 2. Per-request `max_retries` in `request_options` takes precedence over this value. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + logging : typing.Optional[typing.Union[LogConfig, Logger]] + Configure logging for the SDK. Accepts a LogConfig dict with 'level' (debug/info/warn/error), 'logger' (custom logger implementation), and 'silent' (boolean, defaults to True) fields. You can also pass a pre-configured Logger instance. + + Examples + -------- + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + """ + + def __init__( + self, + *, + base_url: typing.Optional[str] = None, + environment: WavixEnvironment = WavixEnvironment.DEFAULT, + token: typing.Union[str, typing.Callable[[], str]], + headers: typing.Optional[typing.Dict[str, str]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + timeout: typing.Optional[float] = None, + max_retries: typing.Optional[int] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + _defaulted_timeout = ( + timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read + ) + _defaulted_max_retries = max_retries if max_retries is not None else 2 + self._client_wrapper = AsyncClientWrapper( + base_url=_get_base_url(base_url=base_url, environment=environment), + token=token, + headers=headers, + async_token=async_token, + httpx_client=httpx_client + if httpx_client is not None + else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), + timeout=_defaulted_timeout, + max_retries=_defaulted_max_retries, + logging=logging, + ) + self._api_keys: typing.Optional[AsyncApiKeysClient] = None + self._sip_trunks: typing.Optional[AsyncSipTrunksClient] = None + self._cart: typing.Optional[AsyncCartClient] = None + self._numbers: typing.Optional[AsyncNumbersClient] = None + self._cdrs: typing.Optional[AsyncCdrsClient] = None + self._call_recording: typing.Optional[AsyncCallRecordingClient] = None + self._speech_analytics: typing.Optional[AsyncSpeechAnalyticsClient] = None + self._call_webhooks: typing.Optional[AsyncCallWebhooksClient] = None + self._call_control: typing.Optional[AsyncCallControlClient] = None + self._number_validator: typing.Optional[AsyncNumberValidatorClient] = None + self._voice_campaigns: typing.Optional[AsyncVoiceCampaignsClient] = None + self._link_shortener: typing.Optional[AsyncLinkShortenerClient] = None + self._profile: typing.Optional[AsyncProfileClient] = None + self._sub_accounts: typing.Optional[AsyncSubAccountsClient] = None + self._sms_and_mms: typing.Optional[AsyncSmsAndMmsClient] = None + self._billing: typing.Optional[AsyncBillingClient] = None + self._buy: typing.Optional[AsyncBuyClient] = None + self._ten_dlc: typing.Optional[AsyncTenDlcClient] = None + self._two_fa: typing.Optional[AsyncTwoFaClient] = None + self._webrtc: typing.Optional[AsyncWebrtcClient] = None + + @property + def api_keys(self): + if self._api_keys is None: + from .api_keys.client import AsyncApiKeysClient # noqa: E402 + + self._api_keys = AsyncApiKeysClient(client_wrapper=self._client_wrapper) + return self._api_keys + + @property + def sip_trunks(self): + if self._sip_trunks is None: + from .sip_trunks.client import AsyncSipTrunksClient # noqa: E402 + + self._sip_trunks = AsyncSipTrunksClient(client_wrapper=self._client_wrapper) + return self._sip_trunks + + @property + def cart(self): + if self._cart is None: + from .cart.client import AsyncCartClient # noqa: E402 + + self._cart = AsyncCartClient(client_wrapper=self._client_wrapper) + return self._cart + + @property + def numbers(self): + if self._numbers is None: + from .numbers.client import AsyncNumbersClient # noqa: E402 + + self._numbers = AsyncNumbersClient(client_wrapper=self._client_wrapper) + return self._numbers + + @property + def cdrs(self): + if self._cdrs is None: + from .cdrs.client import AsyncCdrsClient # noqa: E402 + + self._cdrs = AsyncCdrsClient(client_wrapper=self._client_wrapper) + return self._cdrs + + @property + def call_recording(self): + if self._call_recording is None: + from .call_recording.client import AsyncCallRecordingClient # noqa: E402 + + self._call_recording = AsyncCallRecordingClient(client_wrapper=self._client_wrapper) + return self._call_recording + + @property + def speech_analytics(self): + if self._speech_analytics is None: + from .speech_analytics.client import AsyncSpeechAnalyticsClient # noqa: E402 + + self._speech_analytics = AsyncSpeechAnalyticsClient(client_wrapper=self._client_wrapper) + return self._speech_analytics + + @property + def call_webhooks(self): + if self._call_webhooks is None: + from .call_webhooks.client import AsyncCallWebhooksClient # noqa: E402 + + self._call_webhooks = AsyncCallWebhooksClient(client_wrapper=self._client_wrapper) + return self._call_webhooks + + @property + def call_control(self): + if self._call_control is None: + from .call_control.client import AsyncCallControlClient # noqa: E402 + + self._call_control = AsyncCallControlClient(client_wrapper=self._client_wrapper) + return self._call_control + + @property + def number_validator(self): + if self._number_validator is None: + from .number_validator.client import AsyncNumberValidatorClient # noqa: E402 + + self._number_validator = AsyncNumberValidatorClient(client_wrapper=self._client_wrapper) + return self._number_validator + + @property + def voice_campaigns(self): + if self._voice_campaigns is None: + from .voice_campaigns.client import AsyncVoiceCampaignsClient # noqa: E402 + + self._voice_campaigns = AsyncVoiceCampaignsClient(client_wrapper=self._client_wrapper) + return self._voice_campaigns + + @property + def link_shortener(self): + if self._link_shortener is None: + from .link_shortener.client import AsyncLinkShortenerClient # noqa: E402 + + self._link_shortener = AsyncLinkShortenerClient(client_wrapper=self._client_wrapper) + return self._link_shortener + + @property + def profile(self): + if self._profile is None: + from .profile.client import AsyncProfileClient # noqa: E402 + + self._profile = AsyncProfileClient(client_wrapper=self._client_wrapper) + return self._profile + + @property + def sub_accounts(self): + if self._sub_accounts is None: + from .sub_accounts.client import AsyncSubAccountsClient # noqa: E402 + + self._sub_accounts = AsyncSubAccountsClient(client_wrapper=self._client_wrapper) + return self._sub_accounts + + @property + def sms_and_mms(self): + if self._sms_and_mms is None: + from .sms_and_mms.client import AsyncSmsAndMmsClient # noqa: E402 + + self._sms_and_mms = AsyncSmsAndMmsClient(client_wrapper=self._client_wrapper) + return self._sms_and_mms + + @property + def billing(self): + if self._billing is None: + from .billing.client import AsyncBillingClient # noqa: E402 + + self._billing = AsyncBillingClient(client_wrapper=self._client_wrapper) + return self._billing + + @property + def buy(self): + if self._buy is None: + from .buy.client import AsyncBuyClient # noqa: E402 + + self._buy = AsyncBuyClient(client_wrapper=self._client_wrapper) + return self._buy + + @property + def ten_dlc(self): + if self._ten_dlc is None: + from .ten_dlc.client import AsyncTenDlcClient # noqa: E402 + + self._ten_dlc = AsyncTenDlcClient(client_wrapper=self._client_wrapper) + return self._ten_dlc + + @property + def two_fa(self): + if self._two_fa is None: + from .two_fa.client import AsyncTwoFaClient # noqa: E402 + + self._two_fa = AsyncTwoFaClient(client_wrapper=self._client_wrapper) + return self._two_fa + + @property + def webrtc(self): + if self._webrtc is None: + from .webrtc.client import AsyncWebrtcClient # noqa: E402 + + self._webrtc = AsyncWebrtcClient(client_wrapper=self._client_wrapper) + return self._webrtc + + +def _get_base_url(*, base_url: typing.Optional[str] = None, environment: WavixEnvironment) -> str: + if base_url is not None: + return base_url + elif environment is not None: + return environment.value + else: + raise Exception("Please pass in either base_url or environment to construct the client") diff --git a/src/wavix/core/__init__.py b/src/wavix/core/__init__.py new file mode 100644 index 0000000..5bc159a --- /dev/null +++ b/src/wavix/core/__init__.py @@ -0,0 +1,127 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .api_error import ApiError + from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper + from .datetime_utils import Rfc2822DateTime, parse_rfc2822_datetime, serialize_datetime + from .file import File, convert_file_dict_to_httpx_tuples, with_content_type + from .http_client import AsyncHttpClient, HttpClient + from .http_response import AsyncHttpResponse, HttpResponse + from .jsonable_encoder import encode_path_param, jsonable_encoder + from .logging import ConsoleLogger, ILogger, LogConfig, LogLevel, Logger, create_logger + from .parse_error import ParsingError + from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, + ) + from .query_encoder import encode_query + from .remove_none_from_dict import remove_none_from_dict + from .request_options import RequestOptions + from .serialization import FieldMetadata, convert_and_respect_annotation_metadata +_dynamic_imports: typing.Dict[str, str] = { + "ApiError": ".api_error", + "AsyncClientWrapper": ".client_wrapper", + "AsyncHttpClient": ".http_client", + "AsyncHttpResponse": ".http_response", + "BaseClientWrapper": ".client_wrapper", + "ConsoleLogger": ".logging", + "FieldMetadata": ".serialization", + "File": ".file", + "HttpClient": ".http_client", + "HttpResponse": ".http_response", + "ILogger": ".logging", + "IS_PYDANTIC_V2": ".pydantic_utilities", + "LogConfig": ".logging", + "LogLevel": ".logging", + "Logger": ".logging", + "ParsingError": ".parse_error", + "RequestOptions": ".request_options", + "Rfc2822DateTime": ".datetime_utils", + "SyncClientWrapper": ".client_wrapper", + "UniversalBaseModel": ".pydantic_utilities", + "UniversalRootModel": ".pydantic_utilities", + "convert_and_respect_annotation_metadata": ".serialization", + "convert_file_dict_to_httpx_tuples": ".file", + "create_logger": ".logging", + "encode_path_param": ".jsonable_encoder", + "encode_query": ".query_encoder", + "jsonable_encoder": ".jsonable_encoder", + "parse_obj_as": ".pydantic_utilities", + "parse_rfc2822_datetime": ".datetime_utils", + "remove_none_from_dict": ".remove_none_from_dict", + "serialize_datetime": ".datetime_utils", + "universal_field_validator": ".pydantic_utilities", + "universal_root_validator": ".pydantic_utilities", + "update_forward_refs": ".pydantic_utilities", + "with_content_type": ".file", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "AsyncHttpResponse", + "BaseClientWrapper", + "ConsoleLogger", + "FieldMetadata", + "File", + "HttpClient", + "HttpResponse", + "ILogger", + "IS_PYDANTIC_V2", + "LogConfig", + "LogLevel", + "Logger", + "ParsingError", + "RequestOptions", + "Rfc2822DateTime", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "create_logger", + "encode_path_param", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "parse_rfc2822_datetime", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", + "with_content_type", +] diff --git a/src/wavix/core/api_error.py b/src/wavix/core/api_error.py new file mode 100644 index 0000000..6f850a6 --- /dev/null +++ b/src/wavix/core/api_error.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ApiError(Exception): + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}" diff --git a/src/wavix/core/client_wrapper.py b/src/wavix/core/client_wrapper.py new file mode 100644 index 0000000..d08f70d --- /dev/null +++ b/src/wavix/core/client_wrapper.py @@ -0,0 +1,119 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx +from .http_client import AsyncHttpClient, HttpClient +from .logging import LogConfig, Logger + + +class BaseClientWrapper: + def __init__( + self, + *, + token: typing.Union[str, typing.Callable[[], str]], + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self._token = token + self._headers = headers + self._base_url = base_url + self._timeout = timeout + self._max_retries = max_retries + self._logging = logging + + def get_headers(self) -> typing.Dict[str, str]: + import platform + + headers: typing.Dict[str, str] = { + "User-Agent": "wavix-python-sdk/1.0.0", + "X-Fern-Language": "Python", + "X-Fern-Runtime": f"python/{platform.python_version()}", + "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", + "X-Fern-SDK-Name": "wavix-python-sdk", + "X-Fern-SDK-Version": "1.0.0", + **(self.get_custom_headers() or {}), + } + headers["Authorization"] = f"Bearer {self._get_token()}" + return headers + + def _get_token(self) -> str: + if isinstance(self._token, str): + return self._token + else: + return self._token() + + def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]: + return self._headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + def get_max_retries(self) -> int: + return self._max_retries + + +class SyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + token: typing.Union[str, typing.Callable[[], str]], + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + httpx_client: httpx.Client, + ): + super().__init__( + token=token, headers=headers, base_url=base_url, timeout=timeout, max_retries=max_retries, logging=logging + ) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + base_max_retries=self.get_max_retries(), + logging_config=self._logging, + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__( + self, + *, + token: typing.Union[str, typing.Callable[[], str]], + headers: typing.Optional[typing.Dict[str, str]] = None, + base_url: str, + timeout: typing.Optional[float] = None, + max_retries: int = 2, + logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, + async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, + httpx_client: httpx.AsyncClient, + ): + super().__init__( + token=token, headers=headers, base_url=base_url, timeout=timeout, max_retries=max_retries, logging=logging + ) + self._async_token = async_token + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers, + base_timeout=self.get_timeout, + base_url=self.get_base_url, + base_max_retries=self.get_max_retries(), + async_base_headers=self.async_get_headers, + logging_config=self._logging, + ) + + async def async_get_headers(self) -> typing.Dict[str, str]: + headers = self.get_headers() + if self._async_token is not None: + token = await self._async_token() + headers["Authorization"] = f"Bearer {token}" + return headers diff --git a/src/wavix/core/datetime_utils.py b/src/wavix/core/datetime_utils.py new file mode 100644 index 0000000..a12b2ad --- /dev/null +++ b/src/wavix/core/datetime_utils.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +from email.utils import parsedate_to_datetime +from typing import Any + +import pydantic + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + + +def parse_rfc2822_datetime(v: Any) -> dt.datetime: + """ + Parse an RFC 2822 datetime string (e.g., "Wed, 02 Oct 2002 13:00:00 GMT") + into a datetime object. If the value is already a datetime, return it as-is. + Falls back to ISO 8601 parsing if RFC 2822 parsing fails. + """ + if isinstance(v, dt.datetime): + return v + if isinstance(v, str): + try: + return parsedate_to_datetime(v) + except Exception: + pass + # Fallback to ISO 8601 parsing + return dt.datetime.fromisoformat(v.replace("Z", "+00:00")) + raise ValueError(f"Expected str or datetime, got {type(v)}") + + +class Rfc2822DateTime(dt.datetime): + """A datetime subclass that parses RFC 2822 date strings. + + On Pydantic V1, uses __get_validators__ for pre-validation. + On Pydantic V2, uses __get_pydantic_core_schema__ for BeforeValidator-style parsing. + """ + + @classmethod + def __get_validators__(cls): # type: ignore[no-untyped-def] + yield parse_rfc2822_datetime + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any: # type: ignore[override] + from pydantic_core import core_schema + + return core_schema.no_info_before_validator_function(parse_rfc2822_datetime, core_schema.datetime_schema()) + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/src/wavix/core/file.py b/src/wavix/core/file.py new file mode 100644 index 0000000..44b0d27 --- /dev/null +++ b/src/wavix/core/file.py @@ -0,0 +1,67 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = Union[IO[bytes], bytes, str] +File = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[ + Optional[str], + FileContent, + Optional[str], + Mapping[str, str], + ], +] + + +def convert_file_dict_to_httpx_tuples( + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples + + +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, default_content_type) + elif len(file) == 3: + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) + elif len(file) == 4: + filename, content, file_content_type, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, default_content_type) diff --git a/src/wavix/core/force_multipart.py b/src/wavix/core/force_multipart.py new file mode 100644 index 0000000..5440913 --- /dev/null +++ b/src/wavix/core/force_multipart.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict + + +class ForceMultipartDict(Dict[str, Any]): + """ + A dictionary subclass that always evaluates to True in boolean contexts. + + This is used to force multipart/form-data encoding in HTTP requests even when + the dictionary is empty, which would normally evaluate to False. + """ + + def __bool__(self) -> bool: + return True + + +FORCE_MULTIPART = ForceMultipartDict() diff --git a/src/wavix/core/http_client.py b/src/wavix/core/http_client.py new file mode 100644 index 0000000..f686c57 --- /dev/null +++ b/src/wavix/core/http_client.py @@ -0,0 +1,839 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import re +import time +import typing +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx +from .file import File, convert_file_dict_to_httpx_tuples +from .force_multipart import FORCE_MULTIPART +from .jsonable_encoder import jsonable_encoder +from .logging import LogConfig, Logger, create_logger +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict as remove_none_from_dict +from .request_options import RequestOptions +from httpx._types import RequestFiles + +INITIAL_RETRY_DELAY_SECONDS = 1.0 +MAX_RETRY_DELAY_SECONDS = 60.0 +JITTER_FACTOR = 0.2 # 20% random jitter + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _add_positive_jitter(delay: float) -> float: + """Add positive jitter (0-20%) to prevent thundering herd.""" + jitter_multiplier = 1 + random() * JITTER_FACTOR + return delay * jitter_multiplier + + +def _add_symmetric_jitter(delay: float) -> float: + """Add symmetric jitter (±10%) for exponential backoff.""" + jitter_multiplier = 1 + (random() - 0.5) * JITTER_FACTOR + return delay * jitter_multiplier + + +def _parse_x_ratelimit_reset(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + Parse the X-RateLimit-Reset header (Unix timestamp in seconds). + Returns seconds to wait, or None if header is missing/invalid. + """ + reset_time_str = response_headers.get("x-ratelimit-reset") + if reset_time_str is None: + return None + + try: + reset_time = int(reset_time_str) + delay = reset_time - time.time() + if delay > 0: + return delay + except (ValueError, TypeError): + pass + + return None + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # 1. Check Retry-After header first + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after > 0: + return min(retry_after, MAX_RETRY_DELAY_SECONDS) + + # 2. Check X-RateLimit-Reset header (with positive jitter) + ratelimit_reset = _parse_x_ratelimit_reset(response.headers) + if ratelimit_reset is not None: + return _add_positive_jitter(min(ratelimit_reset, MAX_RETRY_DELAY_SECONDS)) + + # 3. Fall back to exponential backoff (with symmetric jitter) + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _retry_timeout_from_retries(retries: int) -> float: + """Determine retry timeout using exponential backoff when no response is available.""" + backoff = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + return _add_symmetric_jitter(backoff) + + +def _should_retry(response: httpx.Response) -> bool: + return response.status_code >= 500 or response.status_code in [429, 408, 409] + + +_SENSITIVE_HEADERS = frozenset( + { + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "cookie", + "set-cookie", + "proxy-authorization", + "proxy-authenticate", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token", + } +) + + +def _redact_headers(headers: typing.Dict[str, str]) -> typing.Dict[str, str]: + return {k: ("[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v) for k, v in headers.items()} + + +def _build_url(base_url: str, path: typing.Optional[str]) -> str: + """ + Build a full URL by joining a base URL with a path. + + This function correctly handles base URLs that contain path prefixes (e.g., tenant-based URLs) + by using string concatenation instead of urllib.parse.urljoin(), which would incorrectly + strip path components when the path starts with '/'. + + Example: + >>> _build_url("https://cloud.example.com/org/tenant/api", "/users") + 'https://cloud.example.com/org/tenant/api/users' + + Args: + base_url: The base URL, which may contain path prefixes. + path: The path to append. Can be None or empty string. + + Returns: + The full URL with base_url and path properly joined. + """ + if not path: + return base_url + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +def _maybe_filter_none_from_multipart_data( + data: typing.Optional[typing.Any], + request_files: typing.Optional[RequestFiles], + force_multipart: typing.Optional[bool], +) -> typing.Optional[typing.Any]: + """ + Filter None values from data body for multipart/form requests. + This prevents httpx from converting None to empty strings in multipart encoding. + Only applies when files are present or force_multipart is True. + """ + if data is not None and isinstance(data, typing.Mapping) and (request_files or force_multipart): + return remove_none_from_dict(data) + return data + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], + omit: typing.Optional[typing.Any], +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + has_additional_body_parameters = bool( + request_options is not None and request_options.get("additional_body_parameters") + ) + + # Only collapse empty dict to None when the body was not explicitly provided + # and there are no additional body parameters. This preserves explicit empty + # bodies (e.g., when an endpoint has a request body type but all fields are optional). + if json_body == {} and json is None and not has_additional_body_parameters: + json_body = None + if data_body == {} and data is None and not has_additional_body_parameters: + data_body = None + + return json_body, data_body + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + time.sleep(_retry_timeout_from_retries(retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers(), + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Callable[[], typing.Optional[float]], + base_headers: typing.Callable[[], typing.Dict[str, str]], + base_url: typing.Optional[typing.Callable[[], str]] = None, + base_max_retries: int = 2, + async_base_headers: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Dict[str, str]]]] = None, + logging_config: typing.Optional[typing.Union[LogConfig, Logger]] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.base_max_retries = base_max_retries + self.async_base_headers = async_base_headers + self.httpx_client = httpx_client + self.logger = create_logger(logging_config) + + async def _get_headers(self) -> typing.Dict[str, str]: + if self.async_base_headers is not None: + return await self.async_base_headers() + return self.base_headers() + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = maybe_base_url + if self.base_url is not None and base_url is None: + base_url = self.base_url() + + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + has_body=json_body is not None or data_body is not None, + ) + + max_retries: int = ( + request_options.get("max_retries", self.base_max_retries) + if request_options is not None + else self.base_max_retries + ) + + try: + response = await self.httpx_client.request( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError): + if retries < max_retries: + await asyncio.sleep(_retry_timeout_from_retries(retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + raise + + if _should_retry(response=response): + if retries < max_retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + data=data, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + force_multipart=force_multipart, + ) + + if self.logger.is_debug(): + if 200 <= response.status_code < 400: + self.logger.debug( + "HTTP request succeeded", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + if self.logger.is_error(): + if response.status_code >= 400: + self.logger.error( + "HTTP request failed with error status", + method=method, + url=_request_url, + status_code=response.status_code, + ) + + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[ + typing.Union[ + typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]], + typing.List[typing.Tuple[str, File]], + ] + ] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + force_multipart: typing.Optional[bool] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout() + ) + + request_files: typing.Optional[RequestFiles] = ( + convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) + if (files is not None and files is not omit and isinstance(files, dict)) + else None + ) + + if (request_files is None or len(request_files) == 0) and force_multipart: + request_files = FORCE_MULTIPART + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + data_body = _maybe_filter_none_from_multipart_data(data_body, request_files, force_multipart) + + # Get headers (supports async token providers) + _headers = await self._get_headers() + + # Compute encoded params separately to avoid passing empty list to httpx + # (httpx strips existing query params from URL when params=[] is passed) + _encoded_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ) + + _request_url = _build_url(base_url, path) + _request_headers = jsonable_encoder( + remove_none_from_dict( + { + **_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ) + + if self.logger.is_debug(): + self.logger.debug( + "Making streaming HTTP request", + method=method, + url=_request_url, + headers=_redact_headers(_request_headers), + ) + + async with self.httpx_client.stream( + method=method, + url=_request_url, + headers=_request_headers, + params=_encoded_params if _encoded_params else None, + json=json_body, + data=data_body, + content=content, + files=request_files, + timeout=timeout, + ) as stream: + yield stream diff --git a/src/wavix/core/http_response.py b/src/wavix/core/http_response.py new file mode 100644 index 0000000..00bb109 --- /dev/null +++ b/src/wavix/core/http_response.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Dict, Generic, TypeVar + +import httpx + +# Generic to represent the underlying type of the data wrapped by the HTTP response. +T = TypeVar("T") + + +class BaseHttpResponse: + """Minimalist HTTP response wrapper that exposes response headers and status code.""" + + _response: httpx.Response + + def __init__(self, response: httpx.Response): + self._response = response + + @property + def headers(self) -> Dict[str, str]: + return dict(self._response.headers) + + @property + def status_code(self) -> int: + return self._response.status_code + + +class HttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + def close(self) -> None: + self._response.close() + + +class AsyncHttpResponse(Generic[T], BaseHttpResponse): + """HTTP response wrapper that exposes response headers and data.""" + + _data: T + + def __init__(self, response: httpx.Response, data: T): + super().__init__(response) + self._data = data + + @property + def data(self) -> T: + return self._data + + async def close(self) -> None: + await self._response.aclose() diff --git a/src/wavix/core/http_sse/__init__.py b/src/wavix/core/http_sse/__init__.py new file mode 100644 index 0000000..730e5a3 --- /dev/null +++ b/src/wavix/core/http_sse/__init__.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from ._api import EventSource, aconnect_sse, connect_sse + from ._exceptions import SSEError + from ._models import ServerSentEvent +_dynamic_imports: typing.Dict[str, str] = { + "EventSource": "._api", + "SSEError": "._exceptions", + "ServerSentEvent": "._models", + "aconnect_sse": "._api", + "connect_sse": "._api", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["EventSource", "SSEError", "ServerSentEvent", "aconnect_sse", "connect_sse"] diff --git a/src/wavix/core/http_sse/_api.py b/src/wavix/core/http_sse/_api.py new file mode 100644 index 0000000..fd13730 --- /dev/null +++ b/src/wavix/core/http_sse/_api.py @@ -0,0 +1,170 @@ +# This file was auto-generated by Fern from our API Definition. + +import codecs +import re +from contextlib import asynccontextmanager, contextmanager +from typing import Any, AsyncGenerator, AsyncIterator, Iterator + +import httpx +from ._decoders import SSEDecoder +from ._exceptions import SSEError +from ._models import ServerSentEvent + +MAX_LINE_SIZE: int = 1_048_576 # 1 MiB + + +class EventSource: + def __init__(self, response: httpx.Response) -> None: + self._response = response + + def _check_content_type(self) -> None: + content_type = self._response.headers.get("content-type", "").partition(";")[0] + if "text/event-stream" not in content_type: + raise SSEError( + f"Expected response header Content-Type to contain 'text/event-stream', got {content_type!r}" + ) + + def _get_charset(self) -> str: + """Extract charset from Content-Type header, fallback to UTF-8.""" + content_type = self._response.headers.get("content-type", "") + + # Parse charset parameter using regex + charset_match = re.search(r"charset=([^;\s]+)", content_type, re.IGNORECASE) + if charset_match: + charset = charset_match.group(1).strip("\"'") + # Validate that it's a known encoding + try: + # Test if the charset is valid by trying to encode/decode + "test".encode(charset).decode(charset) + return charset + except (LookupError, UnicodeError): + # If charset is invalid, fall back to UTF-8 + pass + + # Default to UTF-8 if no charset specified or invalid charset + return "utf-8" + + @property + def response(self) -> httpx.Response: + return self._response + + @staticmethod + def _normalize_sse_line_endings(buf: str) -> str: + """Normalize line endings per the SSE spec (\\r\\n → \\n, bare \\r → \\n). + + A trailing \\r is preserved because it may pair with a leading \\n in + the next chunk to form a single \\r\\n terminator. + """ + buf = buf.replace("\r\n", "\n") + if buf.endswith("\r"): + return buf[:-1].replace("\r", "\n") + "\r" + return buf.replace("\r", "\n") + + def iter_sse(self) -> Iterator[ServerSentEvent]: + self._check_content_type() + decoder = SSEDecoder() + charset = self._get_charset() + text_decoder = codecs.getincrementaldecoder(charset)(errors="replace") + + buf = "" + for chunk in self._response.iter_bytes(): + buf += text_decoder.decode(chunk) + buf = self._normalize_sse_line_endings(buf) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + # Flush any remaining bytes from the incremental decoder + buf += text_decoder.decode(b"", final=True) + buf = buf.replace("\r\n", "\n").replace("\r", "\n") + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if buf.strip(): + sse = decoder.decode(buf) + if sse is not None: + yield sse + + async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: + self._check_content_type() + decoder = SSEDecoder() + charset = self._get_charset() + text_decoder = codecs.getincrementaldecoder(charset)(errors="replace") + + buf = "" + async for chunk in self._response.aiter_bytes(): + buf += text_decoder.decode(chunk) + buf = self._normalize_sse_line_endings(buf) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + # Flush any remaining bytes from the incremental decoder + buf += text_decoder.decode(b"", final=True) + buf = buf.replace("\r\n", "\n").replace("\r", "\n") + + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + + while "\n" in buf: + line, buf = buf.split("\n", 1) + sse = decoder.decode(line) + if sse is not None: + yield sse + + if buf.strip(): + sse = decoder.decode(buf) + if sse is not None: + yield sse + + +@contextmanager +def connect_sse(client: httpx.Client, method: str, url: str, **kwargs: Any) -> Iterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) + + +@asynccontextmanager +async def aconnect_sse( + client: httpx.AsyncClient, + method: str, + url: str, + **kwargs: Any, +) -> AsyncIterator[EventSource]: + headers = kwargs.pop("headers", {}) + headers["Accept"] = "text/event-stream" + headers["Cache-Control"] = "no-store" + + async with client.stream(method, url, headers=headers, **kwargs) as response: + yield EventSource(response) diff --git a/src/wavix/core/http_sse/_decoders.py b/src/wavix/core/http_sse/_decoders.py new file mode 100644 index 0000000..339b089 --- /dev/null +++ b/src/wavix/core/http_sse/_decoders.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import List, Optional + +from ._models import ServerSentEvent + + +class SSEDecoder: + def __init__(self) -> None: + self._event = "" + self._data: List[str] = [] + self._last_event_id = "" + self._retry: Optional[int] = None + + def decode(self, line: str) -> Optional[ServerSentEvent]: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = "" + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None diff --git a/src/wavix/core/http_sse/_exceptions.py b/src/wavix/core/http_sse/_exceptions.py new file mode 100644 index 0000000..81605a8 --- /dev/null +++ b/src/wavix/core/http_sse/_exceptions.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import httpx + + +class SSEError(httpx.TransportError): + pass diff --git a/src/wavix/core/http_sse/_models.py b/src/wavix/core/http_sse/_models.py new file mode 100644 index 0000000..1af57f8 --- /dev/null +++ b/src/wavix/core/http_sse/_models.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +from dataclasses import dataclass +from typing import Any, Optional + + +@dataclass(frozen=True) +class ServerSentEvent: + event: str = "message" + data: str = "" + id: str = "" + retry: Optional[int] = None + + def json(self) -> Any: + """Parse the data field as JSON.""" + return json.loads(self.data) diff --git a/src/wavix/core/jsonable_encoder.py b/src/wavix/core/jsonable_encoder.py new file mode 100644 index 0000000..5b0902e --- /dev/null +++ b/src/wavix/core/jsonable_encoder.py @@ -0,0 +1,120 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + encode_by_type, + to_jsonable_with_fallback, +) + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT". + # OMIT values should be excluded from serialized payloads. + if obj is Ellipsis: + return None + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + if value is Ellipsis: + continue + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + if item is Ellipsis: + continue + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) + + +def encode_path_param(obj: Any) -> str: + """Encode a value for use in a URL path segment. + + Ensures proper string conversion for all types, including + booleans which need lowercase 'true'/'false' rather than + Python's 'True'/'False'. + """ + if isinstance(obj, bool): + return "true" if obj else "false" + return str(jsonable_encoder(obj)) diff --git a/src/wavix/core/logging.py b/src/wavix/core/logging.py new file mode 100644 index 0000000..e5e5724 --- /dev/null +++ b/src/wavix/core/logging.py @@ -0,0 +1,107 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging +import typing + +LogLevel = typing.Literal["debug", "info", "warn", "error"] + +_LOG_LEVEL_MAP: typing.Dict[LogLevel, int] = { + "debug": 1, + "info": 2, + "warn": 3, + "error": 4, +} + + +class ILogger(typing.Protocol): + def debug(self, message: str, **kwargs: typing.Any) -> None: ... + def info(self, message: str, **kwargs: typing.Any) -> None: ... + def warn(self, message: str, **kwargs: typing.Any) -> None: ... + def error(self, message: str, **kwargs: typing.Any) -> None: ... + + +class ConsoleLogger: + _logger: logging.Logger + + def __init__(self) -> None: + self._logger = logging.getLogger("fern") + if not self._logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s")) + self._logger.addHandler(handler) + self._logger.setLevel(logging.DEBUG) + + def debug(self, message: str, **kwargs: typing.Any) -> None: + self._logger.debug(message, extra=kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + self._logger.info(message, extra=kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + self._logger.warning(message, extra=kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + self._logger.error(message, extra=kwargs) + + +class LogConfig(typing.TypedDict, total=False): + level: LogLevel + logger: ILogger + silent: bool + + +class Logger: + _level: int + _logger: ILogger + _silent: bool + + def __init__(self, *, level: LogLevel, logger: ILogger, silent: bool) -> None: + self._level = _LOG_LEVEL_MAP[level] + self._logger = logger + self._silent = silent + + def _should_log(self, level: LogLevel) -> bool: + return not self._silent and self._level <= _LOG_LEVEL_MAP[level] + + def is_debug(self) -> bool: + return self._should_log("debug") + + def is_info(self) -> bool: + return self._should_log("info") + + def is_warn(self) -> bool: + return self._should_log("warn") + + def is_error(self) -> bool: + return self._should_log("error") + + def debug(self, message: str, **kwargs: typing.Any) -> None: + if self.is_debug(): + self._logger.debug(message, **kwargs) + + def info(self, message: str, **kwargs: typing.Any) -> None: + if self.is_info(): + self._logger.info(message, **kwargs) + + def warn(self, message: str, **kwargs: typing.Any) -> None: + if self.is_warn(): + self._logger.warn(message, **kwargs) + + def error(self, message: str, **kwargs: typing.Any) -> None: + if self.is_error(): + self._logger.error(message, **kwargs) + + +_default_logger: Logger = Logger(level="info", logger=ConsoleLogger(), silent=True) + + +def create_logger(config: typing.Optional[typing.Union[LogConfig, Logger]] = None) -> Logger: + if config is None: + return _default_logger + if isinstance(config, Logger): + return config + return Logger( + level=config.get("level", "info"), + logger=config.get("logger", ConsoleLogger()), + silent=config.get("silent", True), + ) diff --git a/src/wavix/core/parse_error.py b/src/wavix/core/parse_error.py new file mode 100644 index 0000000..4527c6a --- /dev/null +++ b/src/wavix/core/parse_error.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Optional + + +class ParsingError(Exception): + """ + Raised when the SDK fails to parse/validate a response from the server. + This typically indicates that the server returned a response whose shape + does not match the expected schema. + """ + + headers: Optional[Dict[str, str]] + status_code: Optional[int] + body: Any + cause: Optional[Exception] + + def __init__( + self, + *, + headers: Optional[Dict[str, str]] = None, + status_code: Optional[int] = None, + body: Any = None, + cause: Optional[Exception] = None, + ) -> None: + self.headers = headers + self.status_code = status_code + self.body = body + self.cause = cause + super().__init__() + if cause is not None: + self.__cause__ = cause + + def __str__(self) -> str: + cause_str = f", cause: {self.cause}" if self.cause is not None else "" + return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}{cause_str}" diff --git a/src/wavix/core/pydantic_utilities.py b/src/wavix/core/pydantic_utilities.py new file mode 100644 index 0000000..6587f5e --- /dev/null +++ b/src/wavix/core/pydantic_utilities.py @@ -0,0 +1,508 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import inspect +import json +import logging +from collections import defaultdict +from dataclasses import asdict +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +import pydantic +import typing_extensions +from pydantic.fields import FieldInfo as _FieldInfo + +_logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from .http_sse._models import ServerSentEvent + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + _datetime_adapter = pydantic.TypeAdapter(dt.datetime) # type: ignore[attr-defined] + _date_adapter = pydantic.TypeAdapter(dt.date) # type: ignore[attr-defined] + + def parse_datetime(value: Any) -> dt.datetime: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value + return _datetime_adapter.validate_python(value) + + def parse_date(value: Any) -> dt.date: # type: ignore[misc] + if isinstance(value, dt.datetime): + return value.date() + if isinstance(value, dt.date): + return value + return _date_adapter.validate_python(value) + + # Avoid importing from pydantic.v1 to maintain Python 3.14 compatibility. + from typing import get_args as get_args # type: ignore[assignment] + from typing import get_origin as get_origin # type: ignore[assignment] + + def is_literal_type(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return typing_extensions.get_origin(tp) is typing_extensions.Literal + + def is_union(tp: Optional[Type[Any]]) -> bool: # type: ignore[misc] + return tp is Union or typing_extensions.get_origin(tp) is Union # type: ignore[comparison-overlap] + + # Inline encoders_by_type to avoid importing from pydantic.v1.json + import re as _re + from collections import deque as _deque + from decimal import Decimal as _Decimal + from enum import Enum as _Enum + from ipaddress import ( + IPv4Address as _IPv4Address, + ) + from ipaddress import ( + IPv4Interface as _IPv4Interface, + ) + from ipaddress import ( + IPv4Network as _IPv4Network, + ) + from ipaddress import ( + IPv6Address as _IPv6Address, + ) + from ipaddress import ( + IPv6Interface as _IPv6Interface, + ) + from ipaddress import ( + IPv6Network as _IPv6Network, + ) + from pathlib import Path as _Path + from types import GeneratorType as _GeneratorType + from uuid import UUID as _UUID + + from pydantic.fields import FieldInfo as ModelField # type: ignore[no-redef, assignment] + + def _decimal_encoder(dec_value: Any) -> Any: + if dec_value.as_tuple().exponent >= 0: + return int(dec_value) + return float(dec_value) + + encoders_by_type: Dict[Type[Any], Callable[[Any], Any]] = { # type: ignore[no-redef] + bytes: lambda o: o.decode(), + dt.date: lambda o: o.isoformat(), + dt.datetime: lambda o: o.isoformat(), + dt.time: lambda o: o.isoformat(), + dt.timedelta: lambda td: td.total_seconds(), + _Decimal: _decimal_encoder, + _Enum: lambda o: o.value, + frozenset: list, + _deque: list, + _GeneratorType: list, + _IPv4Address: str, + _IPv4Interface: str, + _IPv4Network: str, + _IPv6Address: str, + _IPv6Interface: str, + _IPv6Network: str, + _Path: str, + _re.Pattern: lambda o: o.pattern, + set: list, + _UUID: str, + } +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore[no-redef] + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore[no-redef] + from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined, no-redef, assignment] + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore[no-redef] + from pydantic.typing import get_args as get_args # type: ignore[no-redef] + from pydantic.typing import get_origin as get_origin # type: ignore[no-redef] + from pydantic.typing import is_literal_type as is_literal_type # type: ignore[no-redef, assignment] + from pydantic.typing import is_union as is_union # type: ignore[no-redef] + +from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata +from typing_extensions import TypeAlias + +T = TypeVar("T") +Model = TypeVar("Model", bound=pydantic.BaseModel) + + +def parse_sse_obj(sse: "ServerSentEvent", type_: Type[T]) -> T: + """ + Parse a ServerSentEvent into the appropriate type. + + This function handles data-level discrimination where the discriminator + (e.g., 'type') is inside the 'data' payload. It parses the SSE data field + as JSON and deserializes it into the target type. + + Note: Protocol-level discrimination (where the discriminator comes from + the SSE event: field) is handled at code-generation time and does not + use this function. + + Args: + sse: The ServerSentEvent object to parse + type_: The target type to deserialize into + + Returns: + The parsed object of type T + + Note: + This function is only available in SDK contexts where http_sse module exists. + """ + sse_event = asdict(sse) + data_value = sse_event.get("data") + if isinstance(data_value, str) and data_value: + try: + parsed_data = json.loads(data_value) + return parse_obj_as(type_, parsed_data) + except json.JSONDecodeError as e: + _logger.warning( + "Failed to parse SSE data field as JSON: %s, data: %s", + e, + data_value[:100] if len(data_value) > 100 else data_value, + ) + return parse_obj_as(type_, sse_event) + + +_type_adapter_cache: Dict[int, Any] = {} + + +def _get_type_adapter(type_: Type[Any]) -> Any: + key = id(type_) + adapter = _type_adapter_cache.get(key) + if adapter is None: + adapter = pydantic.TypeAdapter(type_) # type: ignore[attr-defined] + _type_adapter_cache[key] = adapter + return adapter + + +def parse_obj_as(type_: Type[T], object_: Any) -> T: + # convert_and_respect_annotation_metadata is required for TypedDict aliasing. + # + # For Pydantic models, whether we should pre-dealias depends on how the model encodes aliasing: + # - If the model uses real Pydantic aliases (pydantic.Field(alias=...)), then we must pass wire keys through + # unchanged so Pydantic can validate them. + # - If the model encodes aliasing only via FieldMetadata annotations, then we MUST pre-dealias because Pydantic + # will not recognize those aliases during validation. + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + has_pydantic_aliases = False + if IS_PYDANTIC_V2: + for field_name, field_info in getattr(type_, "model_fields", {}).items(): # type: ignore[attr-defined] + alias = getattr(field_info, "alias", None) + if alias is not None and alias != field_name: + has_pydantic_aliases = True + break + else: + for field in getattr(type_, "__fields__", {}).values(): + alias = getattr(field, "alias", None) + name = getattr(field, "name", None) + if alias is not None and name is not None and alias != name: + has_pydantic_aliases = True + break + + dealiased_object = ( + object_ + if has_pydantic_aliases + else convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + ) + else: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") + if IS_PYDANTIC_V2: + adapter = _get_type_adapter(type_) + return adapter.validate_python(dealiased_object) # type: ignore[no-any-return] + return pydantic.parse_obj_as(type_, dealiased_object) + + +def to_jsonable_with_fallback(obj: Any, fallback_serializer: Callable[[Any], Any]) -> Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + if IS_PYDANTIC_V2: + model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( # type: ignore[typeddict-unknown-key] + # Allow fields beginning with `model_` to be used in the model + protected_namespaces=(), + ) + + @pydantic.model_validator(mode="before") # type: ignore[attr-defined] + @classmethod + def _coerce_field_names_to_aliases(cls, data: Any) -> Any: + """ + Accept Python field names in input by rewriting them to their Pydantic aliases, + while avoiding silent collisions when a key could refer to multiple fields. + """ + if not isinstance(data, Mapping): + return data + + fields = getattr(cls, "model_fields", {}) # type: ignore[attr-defined] + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field_info in fields.items(): + alias = getattr(field_info, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + # Detect ambiguous keys: a key that is an alias for one field and a name for another. + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in data and name_to_alias[key] not in data: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(data.keys()) + rewritten: Dict[str, Any] = dict(data) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @pydantic.model_serializer(mode="plain", when_used="json") # type: ignore[attr-defined] + def serialize_model(self) -> Any: # type: ignore[name-defined] + serialized = self.dict() # type: ignore[attr-defined] + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @pydantic.root_validator(pre=True) + def _coerce_field_names_to_aliases(cls, values: Any) -> Any: + """ + Pydantic v1 equivalent of _coerce_field_names_to_aliases. + """ + if not isinstance(values, Mapping): + return values + + fields = getattr(cls, "__fields__", {}) + name_to_alias: Dict[str, str] = {} + alias_to_name: Dict[str, str] = {} + + for name, field in fields.items(): + alias = getattr(field, "alias", None) or name + name_to_alias[name] = alias + if alias != name: + alias_to_name[alias] = name + + ambiguous_keys = set(alias_to_name.keys()).intersection(set(name_to_alias.keys())) + for key in ambiguous_keys: + if key in values and name_to_alias[key] not in values: + raise ValueError( + f"Ambiguous input key '{key}': it is both a field name and an alias. " + "Provide the explicit alias key to disambiguate." + ) + + original_keys = set(values.keys()) + rewritten: Dict[str, Any] = dict(values) + for name, alias in name_to_alias.items(): + if alias != name and name in original_keys and alias not in rewritten: + rewritten[alias] = rewritten.pop(name) + + return rewritten + + @classmethod + def model_construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct(cls: Type["Model"], _fields_set: Optional[Set[str]] = None, **values: Any) -> "Model": + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore[misc] + return super().construct(_fields_set, **dealiased_object) + + def json(self, **kwargs: Any) -> str: + kwargs_with_defaults = { + "by_alias": True, + "exclude_unset": True, + **kwargs, + } + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore[misc] + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: Any) -> Dict[str, Any]: + """ + Override the default dict method to `exclude_unset` by default. This function patches + `exclude_unset` to work include fields within non-None default values. + """ + # Note: the logic here is multiplexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. + if IS_PYDANTIC_V2: + kwargs_with_defaults_exclude_unset = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore[misc] + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore[misc] + ) + + else: + _fields_set = self.__fields_set__.copy() + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]): + _fields_set.add(name) + + if default is not None: + self.__fields_set__.add(name) + + kwargs_with_defaults_exclude_unset_include_fields = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return cast( + Dict[str, Any], + convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write"), + ) + + +def _union_list_of_pydantic_dicts(source: List[Any], destination: List[Any]) -> List[Any]: + converted_list: List[Any] = [] + for i, item in enumerate(source): + destination_value = destination[i] + if isinstance(item, dict): + converted_list.append(deep_union_pydantic_dicts(item, destination_value)) + elif isinstance(item, list): + converted_list.append(_union_list_of_pydantic_dicts(item, destination_value)) + else: + converted_list.append(item) + return converted_list + + +def deep_union_pydantic_dicts(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + for key, value in source.items(): + node = destination.setdefault(key, {}) + if isinstance(value, dict): + deep_union_pydantic_dicts(value, node) + # Note: we do not do this same processing for sets given we do not have sets of models + # and given the sets are unordered, the processing of the set and matching objects would + # be non-trivial. + elif isinstance(value, list): + destination[key] = _union_list_of_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore[misc, name-defined, type-arg] + pass + + UniversalRootModel: TypeAlias = V2RootModel # type: ignore[misc] +else: + UniversalRootModel: TypeAlias = UniversalBaseModel # type: ignore[misc, no-redef] + + +def encode_by_type(o: Any) -> Any: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: Type["Model"], **localns: Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(raise_errors=False) # type: ignore[attr-defined] + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = Callable[..., Any] + + +def universal_root_validator( + pre: bool = False, +) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + # In Pydantic v2, for RootModel we always use "before" mode + # The custom validators transform the input value before the model is created + return cast(AnyCallable, pydantic.model_validator(mode="before")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.root_validator(pre=pre)(func)) # type: ignore[call-overload] + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + if IS_PYDANTIC_V2: + return cast(AnyCallable, pydantic.field_validator(field_name, mode="before" if pre else "after")(func)) # type: ignore[attr-defined] + return cast(AnyCallable, pydantic.validator(field_name, pre=pre)(func)) + + return decorator + + +PydanticField = Union[ModelField, _FieldInfo] + + +def _get_model_fields(model: Type["Model"]) -> Mapping[str, PydanticField]: + if IS_PYDANTIC_V2: + return cast(Mapping[str, PydanticField], model.model_fields) # type: ignore[attr-defined] + return cast(Mapping[str, PydanticField], model.__fields__) + + +def _get_field_default(field: PydanticField) -> Any: + try: + value = field.get_default() # type: ignore[union-attr] + except: + value = field.default + if IS_PYDANTIC_V2: + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + return value diff --git a/src/wavix/core/query_encoder.py b/src/wavix/core/query_encoder.py new file mode 100644 index 0000000..3183001 --- /dev/null +++ b/src/wavix/core/query_encoder.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, List, Optional, Tuple + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> List[Tuple[str, Any]]: + result = [] + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.extend(traverse_query_dict(v, key)) + elif isinstance(v, list): + for arr_v in v: + if isinstance(arr_v, dict): + result.extend(traverse_query_dict(arr_v, key)) + else: + result.append((key, arr_v)) + else: + result.append((key, v)) + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> List[Tuple[str, Any]]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + elif isinstance(query_value, list): + encoded_values: List[Tuple[str, Any]] = [] + for value in query_value: + if isinstance(value, pydantic.BaseModel) or isinstance(value, dict): + if isinstance(value, pydantic.BaseModel): + obj_dict = value.dict(by_alias=True) + elif isinstance(value, dict): + obj_dict = value + + encoded_values.extend(single_query_encoder(query_key, obj_dict)) + else: + encoded_values.append((query_key, value)) + + return encoded_values + + return [(query_key, query_value)] + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[List[Tuple[str, Any]]]: + if query is None: + return None + + encoded_query = [] + for k, v in query.items(): + encoded_query.extend(single_query_encoder(k, v)) + return encoded_query diff --git a/src/wavix/core/remove_none_from_dict.py b/src/wavix/core/remove_none_from_dict.py new file mode 100644 index 0000000..c229814 --- /dev/null +++ b/src/wavix/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/src/wavix/core/request_options.py b/src/wavix/core/request_options.py new file mode 100644 index 0000000..1b38804 --- /dev/null +++ b/src/wavix/core/request_options.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + + - chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads. + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] + chunk_size: NotRequired[int] diff --git a/src/wavix/core/serialization.py b/src/wavix/core/serialization.py new file mode 100644 index 0000000..1d753e2 --- /dev/null +++ b/src/wavix/core/serialization.py @@ -0,0 +1,347 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import inspect +import typing + +import pydantic +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +# Resolving type hints (typing.get_type_hints) is expensive because it eval/compiles +# forward-reference annotations. The result is constant for a given type, so we cache it. +# This is critical for hot paths like SSE event parsing, where the same (often large +# discriminated-union) type is converted on every single event. +_type_hints_cache: typing.Dict[typing.Any, typing.Dict[str, typing.Any]] = {} + + +def _get_cached_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]: + try: + cached = _type_hints_cache.get(expected_type) + except TypeError: + # Unhashable type; resolve without caching. + return _resolve_type_hints(expected_type) + if cached is None: + cached = _resolve_type_hints(expected_type) + _type_hints_cache[expected_type] = cached + return cached + + +def _resolve_type_hints(expected_type: typing.Any) -> typing.Dict[str, typing.Any]: + try: + return typing_extensions.get_type_hints(expected_type, include_extras=True) + except NameError: + # The type contains a circular reference, so we use the __annotations__ attribute directly. + return getattr(expected_type, "__annotations__", {}) + + +# Whether convert_and_respect_annotation_metadata can possibly rewrite anything for a given +# annotation, i.e. whether any reachable model/TypedDict field carries a FieldMetadata alias. +# This is constant per type, so we cache it and use it to short-circuit the recursive walk. +_requires_conversion_cache: typing.Dict[typing.Any, bool] = {} + + +def _requires_conversion(type_: typing.Any) -> bool: + try: + cached = _requires_conversion_cache.get(type_) + except TypeError: + # Unhashable annotation; compute without caching. + return _compute_requires_conversion(type_, set()) + if cached is None: + cached = _compute_requires_conversion(type_, set()) + _requires_conversion_cache[type_] = cached + return cached + + +def _compute_requires_conversion(type_: typing.Any, seen: typing.Set[typing.Any]) -> bool: + clean_type = _remove_annotations(type_) + + try: + if clean_type in seen: + return False + seen = seen | {clean_type} + except TypeError: + # Unhashable type; skip cycle tracking (the type graph is finite in practice). + pass + + # Models / TypedDicts: a field alias here means we must dealias; otherwise recurse into fields. + if (inspect.isclass(clean_type) and issubclass(clean_type, pydantic.BaseModel)) or typing_extensions.is_typeddict( + clean_type + ): + annotations = _get_cached_type_hints(clean_type) + if _get_alias_to_field_name(annotations): + return True + return any(_compute_requires_conversion(hint, seen) for hint in annotations.values()) + + # Containers / unions: recurse into the type arguments (List/Set/Sequence/Dict/Union/etc.). + return any(_compute_requires_conversion(arg, seen) for arg in typing_extensions.get_args(clean_type)) + + +def convert_and_respect_annotation_metadata( + *, + object_: typing.Any, + annotation: typing.Any, + inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + # The only thing this function ever rewrites is keys that carry a FieldMetadata + # alias. If nothing in the (cached) type graph has such an alias, the conversion is + # a content-identity transform, so we can skip the entire recursive walk. This is + # the hot path for SSE streaming, where a large discriminated union would otherwise + # be traversed on every single event. + if not _requires_conversion(annotation): + return object_ + + clean_type = _remove_annotations(inner_type) + # Pydantic models + if ( + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + if ( + typing_extensions.get_origin(clean_type) == typing.Dict + or typing_extensions.get_origin(clean_type) == dict + or clean_type == typing.Dict + ) and isinstance(object_, typing.Dict): + key_type = typing_extensions.get_args(clean_type)[0] + value_type = typing_extensions.get_args(clean_type)[1] + + return { + key: convert_and_respect_annotation_metadata( + object_=value, + annotation=annotation, + inner_type=value_type, + direction=direction, + ) + for key, value in object_.items() + } + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, + ) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = _get_cached_type_hints(expected_type) + aliases_to_field_names = _get_alias_to_field_name(annotations) + for key, value in object_.items(): + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is + if type_ is None: + converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) + else: + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = _get_cached_type_hints(type_) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = _get_cached_type_hints(type_) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + return None + + +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/src/wavix/environment.py b/src/wavix/environment.py new file mode 100644 index 0000000..ac0b61c --- /dev/null +++ b/src/wavix/environment.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum + + +class WavixEnvironment(enum.Enum): + DEFAULT = "https://api.wavix.com" diff --git a/src/wavix/errors/__init__.py b/src/wavix/errors/__init__.py new file mode 100644 index 0000000..911c622 --- /dev/null +++ b/src/wavix/errors/__init__.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .bad_request_error import BadRequestError + from .forbidden_error import ForbiddenError + from .not_found_error import NotFoundError + from .service_unavailable_error import ServiceUnavailableError + from .too_many_requests_error import TooManyRequestsError + from .unauthorized_error import UnauthorizedError + from .unprocessable_entity_error import UnprocessableEntityError +_dynamic_imports: typing.Dict[str, str] = { + "BadRequestError": ".bad_request_error", + "ForbiddenError": ".forbidden_error", + "NotFoundError": ".not_found_error", + "ServiceUnavailableError": ".service_unavailable_error", + "TooManyRequestsError": ".too_many_requests_error", + "UnauthorizedError": ".unauthorized_error", + "UnprocessableEntityError": ".unprocessable_entity_error", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "BadRequestError", + "ForbiddenError", + "NotFoundError", + "ServiceUnavailableError", + "TooManyRequestsError", + "UnauthorizedError", + "UnprocessableEntityError", +] diff --git a/src/wavix/errors/bad_request_error.py b/src/wavix/errors/bad_request_error.py new file mode 100644 index 0000000..ec78e26 --- /dev/null +++ b/src/wavix/errors/bad_request_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class BadRequestError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=400, headers=headers, body=body) diff --git a/src/wavix/errors/forbidden_error.py b/src/wavix/errors/forbidden_error.py new file mode 100644 index 0000000..07d7e45 --- /dev/null +++ b/src/wavix/errors/forbidden_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class ForbiddenError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=403, headers=headers, body=body) diff --git a/src/wavix/errors/not_found_error.py b/src/wavix/errors/not_found_error.py new file mode 100644 index 0000000..75f557d --- /dev/null +++ b/src/wavix/errors/not_found_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class NotFoundError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=404, headers=headers, body=body) diff --git a/src/wavix/errors/service_unavailable_error.py b/src/wavix/errors/service_unavailable_error.py new file mode 100644 index 0000000..01263af --- /dev/null +++ b/src/wavix/errors/service_unavailable_error.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError +from ..types.validation_error_response import ValidationErrorResponse + + +class ServiceUnavailableError(ApiError): + def __init__(self, body: ValidationErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=503, headers=headers, body=body) diff --git a/src/wavix/errors/too_many_requests_error.py b/src/wavix/errors/too_many_requests_error.py new file mode 100644 index 0000000..4a4055c --- /dev/null +++ b/src/wavix/errors/too_many_requests_error.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError +from ..types.validation_error_response import ValidationErrorResponse + + +class TooManyRequestsError(ApiError): + def __init__(self, body: ValidationErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=429, headers=headers, body=body) diff --git a/src/wavix/errors/unauthorized_error.py b/src/wavix/errors/unauthorized_error.py new file mode 100644 index 0000000..78af907 --- /dev/null +++ b/src/wavix/errors/unauthorized_error.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError +from ..types.unauthorized_error_response import UnauthorizedErrorResponse + + +class UnauthorizedError(ApiError): + def __init__(self, body: UnauthorizedErrorResponse, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=401, headers=headers, body=body) diff --git a/src/wavix/errors/unprocessable_entity_error.py b/src/wavix/errors/unprocessable_entity_error.py new file mode 100644 index 0000000..1c801a4 --- /dev/null +++ b/src/wavix/errors/unprocessable_entity_error.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.api_error import ApiError + + +class UnprocessableEntityError(ApiError): + def __init__(self, body: typing.Any, headers: typing.Optional[typing.Dict[str, str]] = None): + super().__init__(status_code=422, headers=headers, body=body) diff --git a/src/wavix/link_shortener/__init__.py b/src/wavix/link_shortener/__init__.py new file mode 100644 index 0000000..47d271a --- /dev/null +++ b/src/wavix/link_shortener/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import metrics +_dynamic_imports: typing.Dict[str, str] = {"metrics": ".metrics"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["metrics"] diff --git a/src/wavix/link_shortener/client.py b/src/wavix/link_shortener/client.py new file mode 100644 index 0000000..20df966 --- /dev/null +++ b/src/wavix/link_shortener/client.py @@ -0,0 +1,194 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import datetime as dt +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.short_link_response import ShortLinkResponse +from .raw_client import AsyncRawLinkShortenerClient, RawLinkShortenerClient + +if typing.TYPE_CHECKING: + from .metrics.client import AsyncMetricsClient, MetricsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class LinkShortenerClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawLinkShortenerClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics: typing.Optional[MetricsClient] = None + + @property + def with_raw_response(self) -> RawLinkShortenerClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawLinkShortenerClient + """ + return self._raw_client + + def create( + self, + *, + link: str, + expiration_time: typing.Optional[dt.datetime] = OMIT, + fallback_url: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + utm_campaign: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ShortLinkResponse: + """ + Creates a short link that redirects to the target URL and tracks click metrics. + + Parameters + ---------- + link : str + Target URL to shorten. + + expiration_time : typing.Optional[dt.datetime] + Expiration date and time in ISO 8601 format. + + fallback_url : typing.Optional[str] + Fallback URL for expired or invalid links. + + phone : typing.Optional[str] + Phone number for the short link. + + utm_campaign : typing.Optional[str] + UTM campaign name for tracking insights. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ShortLinkResponse + Returns the created short link. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.link_shortener.create( + link="https://your-site.com/long-url", + ) + """ + _response = self._raw_client.create( + link=link, + expiration_time=expiration_time, + fallback_url=fallback_url, + phone=phone, + utm_campaign=utm_campaign, + request_options=request_options, + ) + return _response.data + + @property + def metrics(self): + if self._metrics is None: + from .metrics.client import MetricsClient # noqa: E402 + + self._metrics = MetricsClient(client_wrapper=self._client_wrapper) + return self._metrics + + +class AsyncLinkShortenerClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawLinkShortenerClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._metrics: typing.Optional[AsyncMetricsClient] = None + + @property + def with_raw_response(self) -> AsyncRawLinkShortenerClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawLinkShortenerClient + """ + return self._raw_client + + async def create( + self, + *, + link: str, + expiration_time: typing.Optional[dt.datetime] = OMIT, + fallback_url: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + utm_campaign: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ShortLinkResponse: + """ + Creates a short link that redirects to the target URL and tracks click metrics. + + Parameters + ---------- + link : str + Target URL to shorten. + + expiration_time : typing.Optional[dt.datetime] + Expiration date and time in ISO 8601 format. + + fallback_url : typing.Optional[str] + Fallback URL for expired or invalid links. + + phone : typing.Optional[str] + Phone number for the short link. + + utm_campaign : typing.Optional[str] + UTM campaign name for tracking insights. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ShortLinkResponse + Returns the created short link. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.link_shortener.create( + link="https://your-site.com/long-url", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + link=link, + expiration_time=expiration_time, + fallback_url=fallback_url, + phone=phone, + utm_campaign=utm_campaign, + request_options=request_options, + ) + return _response.data + + @property + def metrics(self): + if self._metrics is None: + from .metrics.client import AsyncMetricsClient # noqa: E402 + + self._metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper) + return self._metrics diff --git a/src/wavix/link_shortener/metrics/__init__.py b/src/wavix/link_shortener/metrics/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/link_shortener/metrics/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/link_shortener/metrics/client.py b/src/wavix/link_shortener/metrics/client.py new file mode 100644 index 0000000..c8b48c5 --- /dev/null +++ b/src/wavix/link_shortener/metrics/client.py @@ -0,0 +1,198 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.short_link_metrics_response import ShortLinkMetricsResponse +from .raw_client import AsyncRawMetricsClient, RawMetricsClient + + +class MetricsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMetricsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMetricsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMetricsClient + """ + return self._raw_client + + def list( + self, + *, + from_: dt.date, + to: dt.date, + phone: typing.Optional[str] = None, + utm_campaign: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ShortLinkMetricsResponse: + """ + Returns per-click metrics for short links, including device, location, and campaign attribution, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + phone : typing.Optional[str] + Filters metrics by the phone number associated with the click, in E.164 format. + + utm_campaign : typing.Optional[str] + Filters metrics by `utm_campaign` name. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ShortLinkMetricsResponse + Returns a paginated list of short link metrics. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.link_shortener.metrics.list( + from_=datetime.date.fromisoformat( + "2023-05-01", + ), + to=datetime.date.fromisoformat( + "2023-05-31", + ), + phone="1872025555", + utm_campaign="summer", + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + from_=from_, + to=to, + phone=phone, + utm_campaign=utm_campaign, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + +class AsyncMetricsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMetricsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMetricsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMetricsClient + """ + return self._raw_client + + async def list( + self, + *, + from_: dt.date, + to: dt.date, + phone: typing.Optional[str] = None, + utm_campaign: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ShortLinkMetricsResponse: + """ + Returns per-click metrics for short links, including device, location, and campaign attribution, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + phone : typing.Optional[str] + Filters metrics by the phone number associated with the click, in E.164 format. + + utm_campaign : typing.Optional[str] + Filters metrics by `utm_campaign` name. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ShortLinkMetricsResponse + Returns a paginated list of short link metrics. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.link_shortener.metrics.list( + from_=datetime.date.fromisoformat( + "2023-05-01", + ), + to=datetime.date.fromisoformat( + "2023-05-31", + ), + phone="1872025555", + utm_campaign="summer", + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + from_=from_, + to=to, + phone=phone, + utm_campaign=utm_campaign, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/link_shortener/metrics/raw_client.py b/src/wavix/link_shortener/metrics/raw_client.py new file mode 100644 index 0000000..52c9154 --- /dev/null +++ b/src/wavix/link_shortener/metrics/raw_client.py @@ -0,0 +1,218 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...types.short_link_metrics_response import ShortLinkMetricsResponse +from pydantic import ValidationError + + +class RawMetricsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + from_: dt.date, + to: dt.date, + phone: typing.Optional[str] = None, + utm_campaign: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ShortLinkMetricsResponse]: + """ + Returns per-click metrics for short links, including device, location, and campaign attribution, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + phone : typing.Optional[str] + Filters metrics by the phone number associated with the click, in E.164 format. + + utm_campaign : typing.Optional[str] + Filters metrics by `utm_campaign` name. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ShortLinkMetricsResponse] + Returns a paginated list of short link metrics. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/short-links/metrics", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "phone": phone, + "utm_campaign": utm_campaign, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ShortLinkMetricsResponse, + parse_obj_as( + type_=ShortLinkMetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawMetricsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + from_: dt.date, + to: dt.date, + phone: typing.Optional[str] = None, + utm_campaign: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ShortLinkMetricsResponse]: + """ + Returns per-click metrics for short links, including device, location, and campaign attribution, within the requested date range. + + Parameters + ---------- + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + phone : typing.Optional[str] + Filters metrics by the phone number associated with the click, in E.164 format. + + utm_campaign : typing.Optional[str] + Filters metrics by `utm_campaign` name. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ShortLinkMetricsResponse] + Returns a paginated list of short link metrics. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/short-links/metrics", + method="GET", + params={ + "from": str(from_), + "to": str(to), + "phone": phone, + "utm_campaign": utm_campaign, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ShortLinkMetricsResponse, + parse_obj_as( + type_=ShortLinkMetricsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/link_shortener/raw_client.py b/src/wavix/link_shortener/raw_client.py new file mode 100644 index 0000000..1cd2728 --- /dev/null +++ b/src/wavix/link_shortener/raw_client.py @@ -0,0 +1,265 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.short_link_response import ShortLinkResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawLinkShortenerClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + link: str, + expiration_time: typing.Optional[dt.datetime] = OMIT, + fallback_url: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + utm_campaign: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ShortLinkResponse]: + """ + Creates a short link that redirects to the target URL and tracks click metrics. + + Parameters + ---------- + link : str + Target URL to shorten. + + expiration_time : typing.Optional[dt.datetime] + Expiration date and time in ISO 8601 format. + + fallback_url : typing.Optional[str] + Fallback URL for expired or invalid links. + + phone : typing.Optional[str] + Phone number for the short link. + + utm_campaign : typing.Optional[str] + UTM campaign name for tracking insights. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ShortLinkResponse] + Returns the created short link. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/short-links", + method="POST", + json={ + "link": link, + "expiration_time": expiration_time, + "fallback_url": fallback_url, + "phone": phone, + "utm_campaign": utm_campaign, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ShortLinkResponse, + parse_obj_as( + type_=ShortLinkResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawLinkShortenerClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + link: str, + expiration_time: typing.Optional[dt.datetime] = OMIT, + fallback_url: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + utm_campaign: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ShortLinkResponse]: + """ + Creates a short link that redirects to the target URL and tracks click metrics. + + Parameters + ---------- + link : str + Target URL to shorten. + + expiration_time : typing.Optional[dt.datetime] + Expiration date and time in ISO 8601 format. + + fallback_url : typing.Optional[str] + Fallback URL for expired or invalid links. + + phone : typing.Optional[str] + Phone number for the short link. + + utm_campaign : typing.Optional[str] + UTM campaign name for tracking insights. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ShortLinkResponse] + Returns the created short link. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/short-links", + method="POST", + json={ + "link": link, + "expiration_time": expiration_time, + "fallback_url": fallback_url, + "phone": phone, + "utm_campaign": utm_campaign, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ShortLinkResponse, + parse_obj_as( + type_=ShortLinkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/number_validator/__init__.py b/src/wavix/number_validator/__init__.py new file mode 100644 index 0000000..da27b6f --- /dev/null +++ b/src/wavix/number_validator/__init__.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import GetNumberValidatorResponse + from . import results +_dynamic_imports: typing.Dict[str, str] = {"GetNumberValidatorResponse": ".types", "results": ".results"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GetNumberValidatorResponse", "results"] diff --git a/src/wavix/number_validator/client.py b/src/wavix/number_validator/client.py new file mode 100644 index 0000000..258df00 --- /dev/null +++ b/src/wavix/number_validator/client.py @@ -0,0 +1,271 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.number_validator_create_bulk_response import NumberValidatorCreateBulkResponse +from ..types.phone_number_validation_type import PhoneNumberValidationType +from .raw_client import AsyncRawNumberValidatorClient, RawNumberValidatorClient +from .types.get_number_validator_response import GetNumberValidatorResponse + +if typing.TYPE_CHECKING: + from .results.client import AsyncResultsClient, ResultsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class NumberValidatorClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawNumberValidatorClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._results: typing.Optional[ResultsClient] = None + + @property + def with_raw_response(self) -> RawNumberValidatorClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawNumberValidatorClient + """ + return self._raw_client + + def get( + self, + *, + phone_number: str, + type: PhoneNumberValidationType, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetNumberValidatorResponse: + """ + Validates a single phone number and returns line type, carrier, portability, and reachability details. + + Parameters + ---------- + phone_number : str + The phone number to validate, in E.164 format with or without the leading `+`. + + type : PhoneNumberValidationType + Depth of validation to perform. Accepts a `PhoneNumberValidationType` value. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetNumberValidatorResponse + Returns the phone number validation details. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.number_validator.get( + phone_number="971569483322", + type="format", + ) + """ + _response = self._raw_client.get(phone_number=phone_number, type=type, request_options=request_options) + return _response.data + + def create_bulk( + self, + *, + phone_numbers: typing.Sequence[str], + type: PhoneNumberValidationType, + async_: bool, + force: bool, + request_options: typing.Optional[RequestOptions] = None, + ) -> NumberValidatorCreateBulkResponse: + """ + Validates a batch of phone numbers. When `async` is `true`, returns a `request_id` to poll for results instead of the validation details. + + Parameters + ---------- + phone_numbers : typing.Sequence[str] + List of phone numbers to get detailed information about. + + type : PhoneNumberValidationType + + async_ : bool + Indicates whether the request should be executed asynchronously. If `true`, the response will include a `request_uuid` that can be used to poll for results. If `false`, the response will include validation results directly. + + force : bool + Indicates whether to force a fresh validation instead of returning a previously cached result. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + NumberValidatorCreateBulkResponse + Returns the validation results, or a `request_id` when validation is asynchronous. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.number_validator.create_bulk( + phone_numbers=["971501390098", "971504359195"], + type="format", + async_=True, + force=True, + ) + """ + _response = self._raw_client.create_bulk( + phone_numbers=phone_numbers, type=type, async_=async_, force=force, request_options=request_options + ) + return _response.data + + @property + def results(self): + if self._results is None: + from .results.client import ResultsClient # noqa: E402 + + self._results = ResultsClient(client_wrapper=self._client_wrapper) + return self._results + + +class AsyncNumberValidatorClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawNumberValidatorClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._results: typing.Optional[AsyncResultsClient] = None + + @property + def with_raw_response(self) -> AsyncRawNumberValidatorClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawNumberValidatorClient + """ + return self._raw_client + + async def get( + self, + *, + phone_number: str, + type: PhoneNumberValidationType, + request_options: typing.Optional[RequestOptions] = None, + ) -> GetNumberValidatorResponse: + """ + Validates a single phone number and returns line type, carrier, portability, and reachability details. + + Parameters + ---------- + phone_number : str + The phone number to validate, in E.164 format with or without the leading `+`. + + type : PhoneNumberValidationType + Depth of validation to perform. Accepts a `PhoneNumberValidationType` value. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetNumberValidatorResponse + Returns the phone number validation details. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.number_validator.get( + phone_number="971569483322", + type="format", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(phone_number=phone_number, type=type, request_options=request_options) + return _response.data + + async def create_bulk( + self, + *, + phone_numbers: typing.Sequence[str], + type: PhoneNumberValidationType, + async_: bool, + force: bool, + request_options: typing.Optional[RequestOptions] = None, + ) -> NumberValidatorCreateBulkResponse: + """ + Validates a batch of phone numbers. When `async` is `true`, returns a `request_id` to poll for results instead of the validation details. + + Parameters + ---------- + phone_numbers : typing.Sequence[str] + List of phone numbers to get detailed information about. + + type : PhoneNumberValidationType + + async_ : bool + Indicates whether the request should be executed asynchronously. If `true`, the response will include a `request_uuid` that can be used to poll for results. If `false`, the response will include validation results directly. + + force : bool + Indicates whether to force a fresh validation instead of returning a previously cached result. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + NumberValidatorCreateBulkResponse + Returns the validation results, or a `request_id` when validation is asynchronous. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.number_validator.create_bulk( + phone_numbers=["971501390098", "971504359195"], + type="format", + async_=True, + force=True, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create_bulk( + phone_numbers=phone_numbers, type=type, async_=async_, force=force, request_options=request_options + ) + return _response.data + + @property + def results(self): + if self._results is None: + from .results.client import AsyncResultsClient # noqa: E402 + + self._results = AsyncResultsClient(client_wrapper=self._client_wrapper) + return self._results diff --git a/src/wavix/number_validator/raw_client.py b/src/wavix/number_validator/raw_client.py new file mode 100644 index 0000000..a756f49 --- /dev/null +++ b/src/wavix/number_validator/raw_client.py @@ -0,0 +1,383 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..types.number_validator_create_bulk_response import NumberValidatorCreateBulkResponse +from ..types.phone_number_validation_type import PhoneNumberValidationType +from .types.get_number_validator_response import GetNumberValidatorResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawNumberValidatorClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, + *, + phone_number: str, + type: PhoneNumberValidationType, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GetNumberValidatorResponse]: + """ + Validates a single phone number and returns line type, carrier, portability, and reachability details. + + Parameters + ---------- + phone_number : str + The phone number to validate, in E.164 format with or without the leading `+`. + + type : PhoneNumberValidationType + Depth of validation to perform. Accepts a `PhoneNumberValidationType` value. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetNumberValidatorResponse] + Returns the phone number validation details. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/validation", + method="GET", + params={ + "phone_number": phone_number, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetNumberValidatorResponse, + parse_obj_as( + type_=GetNumberValidatorResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create_bulk( + self, + *, + phone_numbers: typing.Sequence[str], + type: PhoneNumberValidationType, + async_: bool, + force: bool, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[NumberValidatorCreateBulkResponse]: + """ + Validates a batch of phone numbers. When `async` is `true`, returns a `request_id` to poll for results instead of the validation details. + + Parameters + ---------- + phone_numbers : typing.Sequence[str] + List of phone numbers to get detailed information about. + + type : PhoneNumberValidationType + + async_ : bool + Indicates whether the request should be executed asynchronously. If `true`, the response will include a `request_uuid` that can be used to poll for results. If `false`, the response will include validation results directly. + + force : bool + Indicates whether to force a fresh validation instead of returning a previously cached result. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[NumberValidatorCreateBulkResponse] + Returns the validation results, or a `request_id` when validation is asynchronous. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/validation", + method="POST", + json={ + "phone_numbers": phone_numbers, + "type": type, + "async": async_, + "force": force, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + NumberValidatorCreateBulkResponse, + parse_obj_as( + type_=NumberValidatorCreateBulkResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawNumberValidatorClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, + *, + phone_number: str, + type: PhoneNumberValidationType, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GetNumberValidatorResponse]: + """ + Validates a single phone number and returns line type, carrier, portability, and reachability details. + + Parameters + ---------- + phone_number : str + The phone number to validate, in E.164 format with or without the leading `+`. + + type : PhoneNumberValidationType + Depth of validation to perform. Accepts a `PhoneNumberValidationType` value. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetNumberValidatorResponse] + Returns the phone number validation details. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/validation", + method="GET", + params={ + "phone_number": phone_number, + "type": type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetNumberValidatorResponse, + parse_obj_as( + type_=GetNumberValidatorResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create_bulk( + self, + *, + phone_numbers: typing.Sequence[str], + type: PhoneNumberValidationType, + async_: bool, + force: bool, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[NumberValidatorCreateBulkResponse]: + """ + Validates a batch of phone numbers. When `async` is `true`, returns a `request_id` to poll for results instead of the validation details. + + Parameters + ---------- + phone_numbers : typing.Sequence[str] + List of phone numbers to get detailed information about. + + type : PhoneNumberValidationType + + async_ : bool + Indicates whether the request should be executed asynchronously. If `true`, the response will include a `request_uuid` that can be used to poll for results. If `false`, the response will include validation results directly. + + force : bool + Indicates whether to force a fresh validation instead of returning a previously cached result. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[NumberValidatorCreateBulkResponse] + Returns the validation results, or a `request_id` when validation is asynchronous. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/validation", + method="POST", + json={ + "phone_numbers": phone_numbers, + "type": type, + "async": async_, + "force": force, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + NumberValidatorCreateBulkResponse, + parse_obj_as( + type_=NumberValidatorCreateBulkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/number_validator/results/__init__.py b/src/wavix/number_validator/results/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/number_validator/results/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/number_validator/results/client.py b/src/wavix/number_validator/results/client.py new file mode 100644 index 0000000..e41a882 --- /dev/null +++ b/src/wavix/number_validator/results/client.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.phone_validation_batch_result_response import PhoneValidationBatchResultResponse +from .raw_client import AsyncRawResultsClient, RawResultsClient + + +class ResultsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawResultsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawResultsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawResultsClient + """ + return self._raw_client + + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> PhoneValidationBatchResultResponse: + """ + Returns the results of an asynchronous batch validation identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` returned by the asynchronous bulk validation request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PhoneValidationBatchResultResponse + Returns the batch validation results. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.number_validator.results.get( + request_id="12542c5c-1a17-4d12-a163-5b68543e75f6", + ) + """ + _response = self._raw_client.get(request_id, request_options=request_options) + return _response.data + + +class AsyncResultsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawResultsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawResultsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawResultsClient + """ + return self._raw_client + + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> PhoneValidationBatchResultResponse: + """ + Returns the results of an asynchronous batch validation identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` returned by the asynchronous bulk validation request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + PhoneValidationBatchResultResponse + Returns the batch validation results. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.number_validator.results.get( + request_id="12542c5c-1a17-4d12-a163-5b68543e75f6", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_id, request_options=request_options) + return _response.data diff --git a/src/wavix/number_validator/results/raw_client.py b/src/wavix/number_validator/results/raw_client.py new file mode 100644 index 0000000..a97da94 --- /dev/null +++ b/src/wavix/number_validator/results/raw_client.py @@ -0,0 +1,156 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.phone_validation_batch_result_response import PhoneValidationBatchResultResponse +from pydantic import ValidationError + + +class RawResultsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[PhoneValidationBatchResultResponse]: + """ + Returns the results of an asynchronous batch validation identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` returned by the asynchronous bulk validation request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[PhoneValidationBatchResultResponse] + Returns the batch validation results. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/validation/{encode_path_param(request_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PhoneValidationBatchResultResponse, + parse_obj_as( + type_=PhoneValidationBatchResultResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawResultsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[PhoneValidationBatchResultResponse]: + """ + Returns the results of an asynchronous batch validation identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` returned by the asynchronous bulk validation request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[PhoneValidationBatchResultResponse] + Returns the batch validation results. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/validation/{encode_path_param(request_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + PhoneValidationBatchResultResponse, + parse_obj_as( + type_=PhoneValidationBatchResultResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/number_validator/types/__init__.py b/src/wavix/number_validator/types/__init__.py new file mode 100644 index 0000000..aab5846 --- /dev/null +++ b/src/wavix/number_validator/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .get_number_validator_response import GetNumberValidatorResponse +_dynamic_imports: typing.Dict[str, str] = {"GetNumberValidatorResponse": ".get_number_validator_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GetNumberValidatorResponse"] diff --git a/src/wavix/number_validator/types/get_number_validator_response.py b/src/wavix/number_validator/types/get_number_validator_response.py new file mode 100644 index 0000000..769428d --- /dev/null +++ b/src/wavix/number_validator/types/get_number_validator_response.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class GetNumberValidatorResponse(UniversalBaseModel): + phone_number: str = pydantic.Field() + """ + Phone number. + """ + + valid: bool = pydantic.Field() + """ + Indicates whether the phone number is valid. + """ + + country_code: typing.Optional[str] = pydantic.Field(default=None) + """ + ISO 3166-1 alpha-2 country code of the phone number. + `null` if the number is invalid. + """ + + e164format: typing_extensions.Annotated[ + str, + FieldMetadata(alias="e164_format"), + pydantic.Field(alias="e164_format", description="Phone number in international E.164 format."), + ] + national_format: str = pydantic.Field() + """ + Phone number in the national format of the identified country. + """ + + ported: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number was ported or not. `null` if the phone number is invalid. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Country Code of the phone number carrier. For mobile phone numbers only. `null` if the phone number is invalid. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Network Code of the phone number carrier. For mobile phone numbers only. `null` if the phone number is invalid + """ + + number_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Number type. Possible values are `mobile`, `landline`, or `toll-free`. `null` if the phone number is invalid. + """ + + carrier_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Name of the phone number carrier. `null` if the phone number is invalid. + """ + + risky_destination: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to a number range associated with traffic pumping. `null` if the number is invalid. + """ + + unallocated_range: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to an unallocated number range. `null` if the number is invalid + """ + + reachable: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is registered in a mobile network. For mobile phone numbers only. `null` if the number is invalid + """ + + roaming: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is roaming. For mobile phone numbers only. `null` if the number is invalid + """ + + timezone: typing.Optional[str] = pydantic.Field(default=None) + """ + Time zone based on the phone number's country and area code. + `null` if the number is invalid. + """ + + charge: str = pydantic.Field() + """ + Charge for the validation. + """ + + error_code: str = pydantic.Field() + """ + Error code for the request. `000` indicates success. + Possible values: + - `013`: — Internal service error + - `021`: — Invalid phone number length or format + - `041`: — Request timeout + - `042`: — Request failed + - `091`: — Insufficient funds + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/numbers/__init__.py b/src/wavix/numbers/__init__.py new file mode 100644 index 0000000..4b55697 --- /dev/null +++ b/src/wavix/numbers/__init__.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import BulkUpdateNumbersResponse, DeleteNumbersResponse + from . import papers +_dynamic_imports: typing.Dict[str, str] = { + "BulkUpdateNumbersResponse": ".types", + "DeleteNumbersResponse": ".types", + "papers": ".papers", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["BulkUpdateNumbersResponse", "DeleteNumbersResponse", "papers"] diff --git a/src/wavix/numbers/client.py b/src/wavix/numbers/client.py new file mode 100644 index 0000000..b09e992 --- /dev/null +++ b/src/wavix/numbers/client.py @@ -0,0 +1,689 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.number import Number +from ..types.number_destination import NumberDestination +from ..types.number_list_response import NumberListResponse +from .raw_client import AsyncRawNumbersClient, RawNumbersClient +from .types.bulk_update_numbers_response import BulkUpdateNumbersResponse +from .types.delete_numbers_response import DeleteNumbersResponse + +if typing.TYPE_CHECKING: + from .papers.client import AsyncPapersClient, PapersClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class NumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawNumbersClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._papers: typing.Optional[PapersClient] = None + + @property + def with_raw_response(self) -> RawNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawNumbersClient + """ + return self._raw_client + + def list( + self, + *, + city_id: typing.Optional[int] = None, + search: typing.Optional[str] = None, + label: typing.Optional[str] = None, + label_present: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> NumberListResponse: + """ + Returns a paginated list of the phone numbers owned by the authenticated account. + + Parameters + ---------- + city_id : typing.Optional[int] + Filters numbers by the ID of their city or rate center. + + search : typing.Optional[str] + Filters numbers by a full or partial phone number. + + label : typing.Optional[str] + Filters numbers by `label`. + + label_present : typing.Optional[bool] + When `true`, returns only numbers that have a label; when `false`, only numbers without one. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + NumberListResponse + Returns a paginated list of phone numbers. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.list( + city_id=123, + search="256537", + label="ALEX", + label_present=True, + page=2, + per_page=50, + ) + """ + _response = self._raw_client.list( + city_id=city_id, + search=search, + label=label, + label_present=label_present, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def delete( + self, + *, + ids: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + dids: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteNumbersResponse: + """ + Releases the listed phone numbers back to stock. Selection accepts either `ids` (record IDs) or `dids` (phone numbers), but not both. + + Parameters + ---------- + ids : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Record IDs of the phone numbers to release. Mutually exclusive with `dids`. + + dids : typing.Optional[str] + Comma-separated phone numbers to release. Mutually exclusive with `ids`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteNumbersResponse + Returns a success confirmation. The numbers are released back to stock. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.delete( + dids="47832123321,47832123324,478321233215", + ) + """ + _response = self._raw_client.delete(ids=ids, dids=dids, request_options=request_options) + return _response.data + + def bulk_update( + self, + *, + ids: typing.Sequence[int], + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> BulkUpdateNumbersResponse: + """ + Applies the same changes to every listed phone number. Only the provided fields are changed. Destination and SMS callback changes are applied asynchronously and may not be reflected in the response immediately. + + Parameters + ---------- + ids : typing.Sequence[int] + Numbers (by ID) to apply the patch to. The same patch is + applied to every listed number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone numbers. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone numbers. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BulkUpdateNumbersResponse + Returns the updated phone numbers. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.bulk_update( + ids=[123, 456], + ) + """ + _response = self._raw_client.bulk_update( + ids=ids, + sms_enabled=sms_enabled, + destinations=destinations, + sms_relay_url=sms_relay_url, + call_recording_enabled=call_recording_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + call_status_url=call_status_url, + request_options=request_options, + ) + return _response.data + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> Number: + """ + Returns the phone number identified by `id`, including its destinations, documents, and feature settings. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Number + Returns the phone number. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.get( + id=123, + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def update( + self, + id: int, + *, + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Number: + """ + Updates the phone number identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone number. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone number. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Number + Returns the updated phone number. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.update( + id=123, + ) + """ + _response = self._raw_client.update( + id, + sms_enabled=sms_enabled, + destinations=destinations, + sms_relay_url=sms_relay_url, + call_recording_enabled=call_recording_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + call_status_url=call_status_url, + request_options=request_options, + ) + return _response.data + + @property + def papers(self): + if self._papers is None: + from .papers.client import PapersClient # noqa: E402 + + self._papers = PapersClient(client_wrapper=self._client_wrapper) + return self._papers + + +class AsyncNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawNumbersClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._papers: typing.Optional[AsyncPapersClient] = None + + @property + def with_raw_response(self) -> AsyncRawNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawNumbersClient + """ + return self._raw_client + + async def list( + self, + *, + city_id: typing.Optional[int] = None, + search: typing.Optional[str] = None, + label: typing.Optional[str] = None, + label_present: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> NumberListResponse: + """ + Returns a paginated list of the phone numbers owned by the authenticated account. + + Parameters + ---------- + city_id : typing.Optional[int] + Filters numbers by the ID of their city or rate center. + + search : typing.Optional[str] + Filters numbers by a full or partial phone number. + + label : typing.Optional[str] + Filters numbers by `label`. + + label_present : typing.Optional[bool] + When `true`, returns only numbers that have a label; when `false`, only numbers without one. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + NumberListResponse + Returns a paginated list of phone numbers. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.list( + city_id=123, + search="256537", + label="ALEX", + label_present=True, + page=2, + per_page=50, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + city_id=city_id, + search=search, + label=label, + label_present=label_present, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def delete( + self, + *, + ids: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + dids: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> DeleteNumbersResponse: + """ + Releases the listed phone numbers back to stock. Selection accepts either `ids` (record IDs) or `dids` (phone numbers), but not both. + + Parameters + ---------- + ids : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Record IDs of the phone numbers to release. Mutually exclusive with `dids`. + + dids : typing.Optional[str] + Comma-separated phone numbers to release. Mutually exclusive with `ids`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteNumbersResponse + Returns a success confirmation. The numbers are released back to stock. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.delete( + dids="47832123321,47832123324,478321233215", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(ids=ids, dids=dids, request_options=request_options) + return _response.data + + async def bulk_update( + self, + *, + ids: typing.Sequence[int], + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> BulkUpdateNumbersResponse: + """ + Applies the same changes to every listed phone number. Only the provided fields are changed. Destination and SMS callback changes are applied asynchronously and may not be reflected in the response immediately. + + Parameters + ---------- + ids : typing.Sequence[int] + Numbers (by ID) to apply the patch to. The same patch is + applied to every listed number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone numbers. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone numbers. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + BulkUpdateNumbersResponse + Returns the updated phone numbers. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.bulk_update( + ids=[123, 456], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.bulk_update( + ids=ids, + sms_enabled=sms_enabled, + destinations=destinations, + sms_relay_url=sms_relay_url, + call_recording_enabled=call_recording_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + call_status_url=call_status_url, + request_options=request_options, + ) + return _response.data + + async def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> Number: + """ + Returns the phone number identified by `id`, including its destinations, documents, and feature settings. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Number + Returns the phone number. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.get( + id=123, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def update( + self, + id: int, + *, + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> Number: + """ + Updates the phone number identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone number. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone number. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Number + Returns the updated phone number. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.update( + id=123, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, + sms_enabled=sms_enabled, + destinations=destinations, + sms_relay_url=sms_relay_url, + call_recording_enabled=call_recording_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + call_status_url=call_status_url, + request_options=request_options, + ) + return _response.data + + @property + def papers(self): + if self._papers is None: + from .papers.client import AsyncPapersClient # noqa: E402 + + self._papers = AsyncPapersClient(client_wrapper=self._client_wrapper) + return self._papers diff --git a/src/wavix/numbers/papers/__init__.py b/src/wavix/numbers/papers/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/numbers/papers/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/numbers/papers/client.py b/src/wavix/numbers/papers/client.py new file mode 100644 index 0000000..38b98d2 --- /dev/null +++ b/src/wavix/numbers/papers/client.py @@ -0,0 +1,155 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ... import core +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.document_type_id import DocumentTypeId +from ...types.number_document import NumberDocument +from .raw_client import AsyncRawPapersClient, RawPapersClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class PapersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawPapersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawPapersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawPapersClient + """ + return self._raw_client + + def upload( + self, + *, + did_ids: str, + doc_attachment: core.File, + doc_id: DocumentTypeId, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.List[NumberDocument]: + """ + Uploads a verification document for one or more phone numbers. + Uploaded files must meet the following requirements: + - Allowed formats: PNG, JPG, JPEG, TIFF, BMP, or PDF + - Maximum file size: 10 MB + - Files can't be password protected + - PDF files must not contain digital signatures + + Parameters + ---------- + did_ids : str + Comma-separated record IDs of the phone numbers the document applies to. + + doc_attachment : core.File + See core.File for more documentation + + doc_id : DocumentTypeId + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[NumberDocument] + Returns the uploaded documents and their review status. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.numbers.papers.upload( + did_ids="did_ids", + doc_id=1, + ) + """ + _response = self._raw_client.upload( + did_ids=did_ids, doc_attachment=doc_attachment, doc_id=doc_id, request_options=request_options + ) + return _response.data + + +class AsyncPapersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawPapersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawPapersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawPapersClient + """ + return self._raw_client + + async def upload( + self, + *, + did_ids: str, + doc_attachment: core.File, + doc_id: DocumentTypeId, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.List[NumberDocument]: + """ + Uploads a verification document for one or more phone numbers. + Uploaded files must meet the following requirements: + - Allowed formats: PNG, JPG, JPEG, TIFF, BMP, or PDF + - Maximum file size: 10 MB + - Files can't be password protected + - PDF files must not contain digital signatures + + Parameters + ---------- + did_ids : str + Comma-separated record IDs of the phone numbers the document applies to. + + doc_attachment : core.File + See core.File for more documentation + + doc_id : DocumentTypeId + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[NumberDocument] + Returns the uploaded documents and their review status. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.numbers.papers.upload( + did_ids="did_ids", + doc_id=1, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upload( + did_ids=did_ids, doc_attachment=doc_attachment, doc_id=doc_id, request_options=request_options + ) + return _response.data diff --git a/src/wavix/numbers/papers/raw_client.py b/src/wavix/numbers/papers/raw_client.py new file mode 100644 index 0000000..070ac63 --- /dev/null +++ b/src/wavix/numbers/papers/raw_client.py @@ -0,0 +1,208 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ... import core +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...types.document_type_id import DocumentTypeId +from ...types.number_document import NumberDocument +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawPapersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def upload( + self, + *, + did_ids: str, + doc_attachment: core.File, + doc_id: DocumentTypeId, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[typing.List[NumberDocument]]: + """ + Uploads a verification document for one or more phone numbers. + Uploaded files must meet the following requirements: + - Allowed formats: PNG, JPG, JPEG, TIFF, BMP, or PDF + - Maximum file size: 10 MB + - Files can't be password protected + - PDF files must not contain digital signatures + + Parameters + ---------- + did_ids : str + Comma-separated record IDs of the phone numbers the document applies to. + + doc_attachment : core.File + See core.File for more documentation + + doc_id : DocumentTypeId + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[NumberDocument]] + Returns the uploaded documents and their review status. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/numbers/papers", + method="POST", + data={ + "did_ids": did_ids, + "doc_id": doc_id, + }, + files={ + "doc_attachment": doc_attachment, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[NumberDocument], + parse_obj_as( + type_=typing.List[NumberDocument], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawPapersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def upload( + self, + *, + did_ids: str, + doc_attachment: core.File, + doc_id: DocumentTypeId, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[typing.List[NumberDocument]]: + """ + Uploads a verification document for one or more phone numbers. + Uploaded files must meet the following requirements: + - Allowed formats: PNG, JPG, JPEG, TIFF, BMP, or PDF + - Maximum file size: 10 MB + - Files can't be password protected + - PDF files must not contain digital signatures + + Parameters + ---------- + did_ids : str + Comma-separated record IDs of the phone numbers the document applies to. + + doc_attachment : core.File + See core.File for more documentation + + doc_id : DocumentTypeId + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[NumberDocument]] + Returns the uploaded documents and their review status. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/numbers/papers", + method="POST", + data={ + "did_ids": did_ids, + "doc_id": doc_id, + }, + files={ + "doc_attachment": doc_attachment, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[NumberDocument], + parse_obj_as( + type_=typing.List[NumberDocument], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/numbers/raw_client.py b/src/wavix/numbers/raw_client.py new file mode 100644 index 0000000..0fcbf04 --- /dev/null +++ b/src/wavix/numbers/raw_client.py @@ -0,0 +1,1000 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.number import Number +from ..types.number_destination import NumberDestination +from ..types.number_list_response import NumberListResponse +from .types.bulk_update_numbers_response import BulkUpdateNumbersResponse +from .types.delete_numbers_response import DeleteNumbersResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawNumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + city_id: typing.Optional[int] = None, + search: typing.Optional[str] = None, + label: typing.Optional[str] = None, + label_present: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[NumberListResponse]: + """ + Returns a paginated list of the phone numbers owned by the authenticated account. + + Parameters + ---------- + city_id : typing.Optional[int] + Filters numbers by the ID of their city or rate center. + + search : typing.Optional[str] + Filters numbers by a full or partial phone number. + + label : typing.Optional[str] + Filters numbers by `label`. + + label_present : typing.Optional[bool] + When `true`, returns only numbers that have a label; when `false`, only numbers without one. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[NumberListResponse] + Returns a paginated list of phone numbers. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/numbers", + method="GET", + params={ + "city_id": city_id, + "search": search, + "label": label, + "label_present": label_present, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + NumberListResponse, + parse_obj_as( + type_=NumberListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, + *, + ids: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + dids: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DeleteNumbersResponse]: + """ + Releases the listed phone numbers back to stock. Selection accepts either `ids` (record IDs) or `dids` (phone numbers), but not both. + + Parameters + ---------- + ids : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Record IDs of the phone numbers to release. Mutually exclusive with `dids`. + + dids : typing.Optional[str] + Comma-separated phone numbers to release. Mutually exclusive with `ids`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteNumbersResponse] + Returns a success confirmation. The numbers are released back to stock. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/numbers", + method="DELETE", + params={ + "ids": ids, + "dids": dids, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteNumbersResponse, + parse_obj_as( + type_=DeleteNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def bulk_update( + self, + *, + ids: typing.Sequence[int], + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[BulkUpdateNumbersResponse]: + """ + Applies the same changes to every listed phone number. Only the provided fields are changed. Destination and SMS callback changes are applied asynchronously and may not be reflected in the response immediately. + + Parameters + ---------- + ids : typing.Sequence[int] + Numbers (by ID) to apply the patch to. The same patch is + applied to every listed number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone numbers. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone numbers. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[BulkUpdateNumbersResponse] + Returns the updated phone numbers. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/numbers", + method="PATCH", + json={ + "ids": ids, + "sms_enabled": sms_enabled, + "destinations": convert_and_respect_annotation_metadata( + object_=destinations, annotation=typing.Sequence[NumberDestination], direction="write" + ), + "sms_relay_url": sms_relay_url, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "call_status_url": call_status_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BulkUpdateNumbersResponse, + parse_obj_as( + type_=BulkUpdateNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Number]: + """ + Returns the phone number identified by `id`, including its destinations, documents, and feature settings. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Number] + Returns the phone number. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/numbers/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Number, + parse_obj_as( + type_=Number, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + id: int, + *, + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[Number]: + """ + Updates the phone number identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone number. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone number. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[Number] + Returns the updated phone number. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/numbers/{encode_path_param(id)}", + method="PATCH", + json={ + "sms_enabled": sms_enabled, + "destinations": convert_and_respect_annotation_metadata( + object_=destinations, annotation=typing.Sequence[NumberDestination], direction="write" + ), + "sms_relay_url": sms_relay_url, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "call_status_url": call_status_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Number, + parse_obj_as( + type_=Number, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + city_id: typing.Optional[int] = None, + search: typing.Optional[str] = None, + label: typing.Optional[str] = None, + label_present: typing.Optional[bool] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[NumberListResponse]: + """ + Returns a paginated list of the phone numbers owned by the authenticated account. + + Parameters + ---------- + city_id : typing.Optional[int] + Filters numbers by the ID of their city or rate center. + + search : typing.Optional[str] + Filters numbers by a full or partial phone number. + + label : typing.Optional[str] + Filters numbers by `label`. + + label_present : typing.Optional[bool] + When `true`, returns only numbers that have a label; when `false`, only numbers without one. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[NumberListResponse] + Returns a paginated list of phone numbers. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/numbers", + method="GET", + params={ + "city_id": city_id, + "search": search, + "label": label, + "label_present": label_present, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + NumberListResponse, + parse_obj_as( + type_=NumberListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, + *, + ids: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + dids: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DeleteNumbersResponse]: + """ + Releases the listed phone numbers back to stock. Selection accepts either `ids` (record IDs) or `dids` (phone numbers), but not both. + + Parameters + ---------- + ids : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Record IDs of the phone numbers to release. Mutually exclusive with `dids`. + + dids : typing.Optional[str] + Comma-separated phone numbers to release. Mutually exclusive with `ids`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteNumbersResponse] + Returns a success confirmation. The numbers are released back to stock. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/numbers", + method="DELETE", + params={ + "ids": ids, + "dids": dids, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteNumbersResponse, + parse_obj_as( + type_=DeleteNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def bulk_update( + self, + *, + ids: typing.Sequence[int], + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[BulkUpdateNumbersResponse]: + """ + Applies the same changes to every listed phone number. Only the provided fields are changed. Destination and SMS callback changes are applied asynchronously and may not be reflected in the response immediately. + + Parameters + ---------- + ids : typing.Sequence[int] + Numbers (by ID) to apply the patch to. The same patch is + applied to every listed number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone numbers. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone numbers. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[BulkUpdateNumbersResponse] + Returns the updated phone numbers. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/numbers", + method="PATCH", + json={ + "ids": ids, + "sms_enabled": sms_enabled, + "destinations": convert_and_respect_annotation_metadata( + object_=destinations, annotation=typing.Sequence[NumberDestination], direction="write" + ), + "sms_relay_url": sms_relay_url, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "call_status_url": call_status_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + BulkUpdateNumbersResponse, + parse_obj_as( + type_=BulkUpdateNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[Number]: + """ + Returns the phone number identified by `id`, including its destinations, documents, and feature settings. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Number] + Returns the phone number. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/numbers/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Number, + parse_obj_as( + type_=Number, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + id: int, + *, + sms_enabled: typing.Optional[bool] = OMIT, + destinations: typing.Optional[typing.Sequence[NumberDestination]] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + transcription_enabled: typing.Optional[bool] = OMIT, + transcription_threshold: typing.Optional[int] = OMIT, + call_status_url: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[Number]: + """ + Updates the phone number identified by `id`. Only the provided fields are changed. + + Parameters + ---------- + id : int + The unique ID of the phone number. + + sms_enabled : typing.Optional[bool] + Indicates whether SMS is enabled for the phone number. + + destinations : typing.Optional[typing.Sequence[NumberDestination]] + Inbound call routing destinations for the phone number. + + sms_relay_url : typing.Optional[str] + Callback URL for inbound messages. + + call_recording_enabled : typing.Optional[bool] + Indicates whether call recording is enabled. + + transcription_enabled : typing.Optional[bool] + Indicates whether call transcription is enabled. + + transcription_threshold : typing.Optional[int] + Minimum call duration in seconds before transcription runs. + + call_status_url : typing.Optional[str] + Callback URL for call status updates. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[Number] + Returns the updated phone number. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/numbers/{encode_path_param(id)}", + method="PATCH", + json={ + "sms_enabled": sms_enabled, + "destinations": convert_and_respect_annotation_metadata( + object_=destinations, annotation=typing.Sequence[NumberDestination], direction="write" + ), + "sms_relay_url": sms_relay_url, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "call_status_url": call_status_url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + Number, + parse_obj_as( + type_=Number, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/numbers/types/__init__.py b/src/wavix/numbers/types/__init__.py new file mode 100644 index 0000000..545ff00 --- /dev/null +++ b/src/wavix/numbers/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .bulk_update_numbers_response import BulkUpdateNumbersResponse + from .delete_numbers_response import DeleteNumbersResponse +_dynamic_imports: typing.Dict[str, str] = { + "BulkUpdateNumbersResponse": ".bulk_update_numbers_response", + "DeleteNumbersResponse": ".delete_numbers_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["BulkUpdateNumbersResponse", "DeleteNumbersResponse"] diff --git a/src/wavix/numbers/types/bulk_update_numbers_response.py b/src/wavix/numbers/types/bulk_update_numbers_response.py new file mode 100644 index 0000000..9d45c0e --- /dev/null +++ b/src/wavix/numbers/types/bulk_update_numbers_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...types.number import Number + + +class BulkUpdateNumbersResponse(UniversalBaseModel): + items: typing.Optional[typing.List[Number]] = pydantic.Field(default=None) + """ + The updated phone numbers. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/numbers/types/delete_numbers_response.py b/src/wavix/numbers/types/delete_numbers_response.py new file mode 100644 index 0000000..88a5912 --- /dev/null +++ b/src/wavix/numbers/types/delete_numbers_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteNumbersResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/profile/__init__.py b/src/wavix/profile/__init__.py new file mode 100644 index 0000000..edea256 --- /dev/null +++ b/src/wavix/profile/__init__.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ProfileUpdateRequestCompanyInfo, ProfileUpdateRequestCompanyInfoIndustry + from . import config + from .config import GetConfigResponse +_dynamic_imports: typing.Dict[str, str] = { + "GetConfigResponse": ".config", + "ProfileUpdateRequestCompanyInfo": ".types", + "ProfileUpdateRequestCompanyInfoIndustry": ".types", + "config": ".config", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GetConfigResponse", "ProfileUpdateRequestCompanyInfo", "ProfileUpdateRequestCompanyInfoIndustry", "config"] diff --git a/src/wavix/profile/client.py b/src/wavix/profile/client.py new file mode 100644 index 0000000..2b16660 --- /dev/null +++ b/src/wavix/profile/client.py @@ -0,0 +1,308 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.profile_response import ProfileResponse +from .raw_client import AsyncRawProfileClient, RawProfileClient +from .types.profile_update_request_company_info import ProfileUpdateRequestCompanyInfo + +if typing.TYPE_CHECKING: + from .config.client import AsyncConfigClient, ConfigClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ProfileClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawProfileClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._config: typing.Optional[ConfigClient] = None + + @property + def with_raw_response(self) -> RawProfileClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawProfileClient + """ + return self._raw_client + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> ProfileResponse: + """ + Returns the profile and billing details of the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProfileResponse + Returns the profile. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.profile.get() + """ + _response = self._raw_client.get(request_options=request_options) + return _response.data + + def update( + self, + *, + additional_info: typing.Optional[str] = OMIT, + contacts: typing.Optional[str] = OMIT, + default_short_link_endpoint: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + dlr_relay_url: typing.Optional[str] = OMIT, + time_zone: typing.Optional[str] = OMIT, + job_title: typing.Optional[str] = OMIT, + company_info: typing.Optional[ProfileUpdateRequestCompanyInfo] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ProfileResponse: + """ + Updates the profile and billing details of the authenticated account. + + Parameters + ---------- + additional_info : typing.Optional[str] + Additional information associated with the account. + + contacts : typing.Optional[str] + Email associated with the account. + + default_short_link_endpoint : typing.Optional[str] + Default short link endpoint. + + first_name : typing.Optional[str] + Account owner's first name. + + last_name : typing.Optional[str] + Account owner's last name. + + phone : typing.Optional[str] + Account owner's phone number + + sms_relay_url : typing.Optional[str] + Callback URL to forward inbound SMS to. + + dlr_relay_url : typing.Optional[str] + Callback URL to forward message delivery reports (DLRs) to. + + time_zone : typing.Optional[str] + Timezone configured on the account. + + job_title : typing.Optional[str] + Account owner's job title. + + company_info : typing.Optional[ProfileUpdateRequestCompanyInfo] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProfileResponse + Returns the updated profile. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.profile.update() + """ + _response = self._raw_client.update( + additional_info=additional_info, + contacts=contacts, + default_short_link_endpoint=default_short_link_endpoint, + first_name=first_name, + last_name=last_name, + phone=phone, + sms_relay_url=sms_relay_url, + dlr_relay_url=dlr_relay_url, + time_zone=time_zone, + job_title=job_title, + company_info=company_info, + request_options=request_options, + ) + return _response.data + + @property + def config(self): + if self._config is None: + from .config.client import ConfigClient # noqa: E402 + + self._config = ConfigClient(client_wrapper=self._client_wrapper) + return self._config + + +class AsyncProfileClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawProfileClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._config: typing.Optional[AsyncConfigClient] = None + + @property + def with_raw_response(self) -> AsyncRawProfileClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawProfileClient + """ + return self._raw_client + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> ProfileResponse: + """ + Returns the profile and billing details of the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProfileResponse + Returns the profile. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.profile.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_options=request_options) + return _response.data + + async def update( + self, + *, + additional_info: typing.Optional[str] = OMIT, + contacts: typing.Optional[str] = OMIT, + default_short_link_endpoint: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + dlr_relay_url: typing.Optional[str] = OMIT, + time_zone: typing.Optional[str] = OMIT, + job_title: typing.Optional[str] = OMIT, + company_info: typing.Optional[ProfileUpdateRequestCompanyInfo] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> ProfileResponse: + """ + Updates the profile and billing details of the authenticated account. + + Parameters + ---------- + additional_info : typing.Optional[str] + Additional information associated with the account. + + contacts : typing.Optional[str] + Email associated with the account. + + default_short_link_endpoint : typing.Optional[str] + Default short link endpoint. + + first_name : typing.Optional[str] + Account owner's first name. + + last_name : typing.Optional[str] + Account owner's last name. + + phone : typing.Optional[str] + Account owner's phone number + + sms_relay_url : typing.Optional[str] + Callback URL to forward inbound SMS to. + + dlr_relay_url : typing.Optional[str] + Callback URL to forward message delivery reports (DLRs) to. + + time_zone : typing.Optional[str] + Timezone configured on the account. + + job_title : typing.Optional[str] + Account owner's job title. + + company_info : typing.Optional[ProfileUpdateRequestCompanyInfo] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ProfileResponse + Returns the updated profile. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.profile.update() + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + additional_info=additional_info, + contacts=contacts, + default_short_link_endpoint=default_short_link_endpoint, + first_name=first_name, + last_name=last_name, + phone=phone, + sms_relay_url=sms_relay_url, + dlr_relay_url=dlr_relay_url, + time_zone=time_zone, + job_title=job_title, + company_info=company_info, + request_options=request_options, + ) + return _response.data + + @property + def config(self): + if self._config is None: + from .config.client import AsyncConfigClient # noqa: E402 + + self._config = AsyncConfigClient(client_wrapper=self._client_wrapper) + return self._config diff --git a/src/wavix/profile/config/__init__.py b/src/wavix/profile/config/__init__.py new file mode 100644 index 0000000..f19b786 --- /dev/null +++ b/src/wavix/profile/config/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import GetConfigResponse +_dynamic_imports: typing.Dict[str, str] = {"GetConfigResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GetConfigResponse"] diff --git a/src/wavix/profile/config/client.py b/src/wavix/profile/config/client.py new file mode 100644 index 0000000..8446cdd --- /dev/null +++ b/src/wavix/profile/config/client.py @@ -0,0 +1,100 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawConfigClient, RawConfigClient +from .types.get_config_response import GetConfigResponse + + +class ConfigClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawConfigClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawConfigClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawConfigClient + """ + return self._raw_client + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> GetConfigResponse: + """ + Returns the balance and global limits configured for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetConfigResponse + Returns the account settings. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.profile.config.get() + """ + _response = self._raw_client.get(request_options=request_options) + return _response.data + + +class AsyncConfigClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawConfigClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawConfigClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawConfigClient + """ + return self._raw_client + + async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> GetConfigResponse: + """ + Returns the balance and global limits configured for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetConfigResponse + Returns the account settings. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.profile.config.get() + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_options=request_options) + return _response.data diff --git a/src/wavix/profile/config/raw_client.py b/src/wavix/profile/config/raw_client.py new file mode 100644 index 0000000..389f50b --- /dev/null +++ b/src/wavix/profile/config/raw_client.py @@ -0,0 +1,124 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from .types.get_config_response import GetConfigResponse +from pydantic import ValidationError + + +class RawConfigClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[GetConfigResponse]: + """ + Returns the balance and global limits configured for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetConfigResponse] + Returns the account settings. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/profile/config", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetConfigResponse, + parse_obj_as( + type_=GetConfigResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawConfigClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetConfigResponse]: + """ + Returns the balance and global limits configured for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetConfigResponse] + Returns the account settings. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/profile/config", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetConfigResponse, + parse_obj_as( + type_=GetConfigResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/profile/config/types/__init__.py b/src/wavix/profile/config/types/__init__.py new file mode 100644 index 0000000..12d6e40 --- /dev/null +++ b/src/wavix/profile/config/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .get_config_response import GetConfigResponse +_dynamic_imports: typing.Dict[str, str] = {"GetConfigResponse": ".get_config_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GetConfigResponse"] diff --git a/src/wavix/profile/config/types/get_config_response.py b/src/wavix/profile/config/types/get_config_response.py new file mode 100644 index 0000000..e54c07f --- /dev/null +++ b/src/wavix/profile/config/types/get_config_response.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.account_limits import AccountLimits + + +class GetConfigResponse(UniversalBaseModel): + balance: str = pydantic.Field() + """ + Funds available on the account balance, in USD + """ + + global_limits: AccountLimits + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/profile/raw_client.py b/src/wavix/profile/raw_client.py new file mode 100644 index 0000000..2c96af0 --- /dev/null +++ b/src/wavix/profile/raw_client.py @@ -0,0 +1,360 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.forbidden_error import ForbiddenError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.profile_response import ProfileResponse +from .types.profile_update_request_company_info import ProfileUpdateRequestCompanyInfo +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawProfileClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[ProfileResponse]: + """ + Returns the profile and billing details of the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ProfileResponse] + Returns the profile. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/profile", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProfileResponse, + parse_obj_as( + type_=ProfileResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + *, + additional_info: typing.Optional[str] = OMIT, + contacts: typing.Optional[str] = OMIT, + default_short_link_endpoint: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + dlr_relay_url: typing.Optional[str] = OMIT, + time_zone: typing.Optional[str] = OMIT, + job_title: typing.Optional[str] = OMIT, + company_info: typing.Optional[ProfileUpdateRequestCompanyInfo] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ProfileResponse]: + """ + Updates the profile and billing details of the authenticated account. + + Parameters + ---------- + additional_info : typing.Optional[str] + Additional information associated with the account. + + contacts : typing.Optional[str] + Email associated with the account. + + default_short_link_endpoint : typing.Optional[str] + Default short link endpoint. + + first_name : typing.Optional[str] + Account owner's first name. + + last_name : typing.Optional[str] + Account owner's last name. + + phone : typing.Optional[str] + Account owner's phone number + + sms_relay_url : typing.Optional[str] + Callback URL to forward inbound SMS to. + + dlr_relay_url : typing.Optional[str] + Callback URL to forward message delivery reports (DLRs) to. + + time_zone : typing.Optional[str] + Timezone configured on the account. + + job_title : typing.Optional[str] + Account owner's job title. + + company_info : typing.Optional[ProfileUpdateRequestCompanyInfo] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ProfileResponse] + Returns the updated profile. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/profile", + method="PUT", + json={ + "additional_info": additional_info, + "contacts": contacts, + "default_short_link_endpoint": default_short_link_endpoint, + "first_name": first_name, + "last_name": last_name, + "phone": phone, + "sms_relay_url": sms_relay_url, + "dlr_relay_url": dlr_relay_url, + "time_zone": time_zone, + "job_title": job_title, + "company_info": convert_and_respect_annotation_metadata( + object_=company_info, annotation=ProfileUpdateRequestCompanyInfo, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProfileResponse, + parse_obj_as( + type_=ProfileResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawProfileClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ProfileResponse]: + """ + Returns the profile and billing details of the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ProfileResponse] + Returns the profile. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/profile", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProfileResponse, + parse_obj_as( + type_=ProfileResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + *, + additional_info: typing.Optional[str] = OMIT, + contacts: typing.Optional[str] = OMIT, + default_short_link_endpoint: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone: typing.Optional[str] = OMIT, + sms_relay_url: typing.Optional[str] = OMIT, + dlr_relay_url: typing.Optional[str] = OMIT, + time_zone: typing.Optional[str] = OMIT, + job_title: typing.Optional[str] = OMIT, + company_info: typing.Optional[ProfileUpdateRequestCompanyInfo] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ProfileResponse]: + """ + Updates the profile and billing details of the authenticated account. + + Parameters + ---------- + additional_info : typing.Optional[str] + Additional information associated with the account. + + contacts : typing.Optional[str] + Email associated with the account. + + default_short_link_endpoint : typing.Optional[str] + Default short link endpoint. + + first_name : typing.Optional[str] + Account owner's first name. + + last_name : typing.Optional[str] + Account owner's last name. + + phone : typing.Optional[str] + Account owner's phone number + + sms_relay_url : typing.Optional[str] + Callback URL to forward inbound SMS to. + + dlr_relay_url : typing.Optional[str] + Callback URL to forward message delivery reports (DLRs) to. + + time_zone : typing.Optional[str] + Timezone configured on the account. + + job_title : typing.Optional[str] + Account owner's job title. + + company_info : typing.Optional[ProfileUpdateRequestCompanyInfo] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ProfileResponse] + Returns the updated profile. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/profile", + method="PUT", + json={ + "additional_info": additional_info, + "contacts": contacts, + "default_short_link_endpoint": default_short_link_endpoint, + "first_name": first_name, + "last_name": last_name, + "phone": phone, + "sms_relay_url": sms_relay_url, + "dlr_relay_url": dlr_relay_url, + "time_zone": time_zone, + "job_title": job_title, + "company_info": convert_and_respect_annotation_metadata( + object_=company_info, annotation=ProfileUpdateRequestCompanyInfo, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ProfileResponse, + parse_obj_as( + type_=ProfileResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/profile/types/__init__.py b/src/wavix/profile/types/__init__.py new file mode 100644 index 0000000..a6e4103 --- /dev/null +++ b/src/wavix/profile/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .profile_update_request_company_info import ProfileUpdateRequestCompanyInfo + from .profile_update_request_company_info_industry import ProfileUpdateRequestCompanyInfoIndustry +_dynamic_imports: typing.Dict[str, str] = { + "ProfileUpdateRequestCompanyInfo": ".profile_update_request_company_info", + "ProfileUpdateRequestCompanyInfoIndustry": ".profile_update_request_company_info_industry", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["ProfileUpdateRequestCompanyInfo", "ProfileUpdateRequestCompanyInfoIndustry"] diff --git a/src/wavix/profile/types/profile_update_request_company_info.py b/src/wavix/profile/types/profile_update_request_company_info.py new file mode 100644 index 0000000..17a115b --- /dev/null +++ b/src/wavix/profile/types/profile_update_request_company_info.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .profile_update_request_company_info_industry import ProfileUpdateRequestCompanyInfoIndustry + + +class ProfileUpdateRequestCompanyInfo(UniversalBaseModel): + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name. + """ + + industry: typing.Optional[ProfileUpdateRequestCompanyInfoIndustry] = pydantic.Field(default=None) + """ + Industry the company operates in. One of `telecommunications`, `information technology and services`, `fintech and finance`, `healthcare and pharmaceuticals`, `ecommerce and retail`, `education and research`, `pickup and delivery`, `transportation and logistics`, `media and entertainment`, `travel and hospitality`, `non-profit and charity organizations`, `manufacturing and industrial goods`, or `other`. Each value names the company's sector. + """ + + billing_address: typing.Optional[str] = pydantic.Field(default=None) + """ + Billing address. + """ + + attn_contact_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Billing contact name. + """ + + vat_number: typing.Optional[str] = pydantic.Field(default=None) + """ + VAT number. + """ + + country_code: typing.Optional[str] = pydantic.Field(default=None) + """ + Country code. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/profile/types/profile_update_request_company_info_industry.py b/src/wavix/profile/types/profile_update_request_company_info_industry.py new file mode 100644 index 0000000..d053b0d --- /dev/null +++ b/src/wavix/profile/types/profile_update_request_company_info_industry.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProfileUpdateRequestCompanyInfoIndustry = typing.Union[ + typing.Literal[ + "telecommunications", + "information technology and services", + "fintech and finance", + "healthcare and pharmaceuticals", + "ecommerce and retail", + "education and research", + "pickup and delivery", + "transportation and logistics", + "media and entertainment", + "travel and hospitality", + "non-profit and charity organizations", + "manufacturing and industrial goods", + "other", + ], + typing.Any, +] diff --git a/src/wavix/py.typed b/src/wavix/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/wavix/sip_trunks/__init__.py b/src/wavix/sip_trunks/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/sip_trunks/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/sip_trunks/client.py b/src/wavix/sip_trunks/client.py new file mode 100644 index 0000000..d59e488 --- /dev/null +++ b/src/wavix/sip_trunks/client.py @@ -0,0 +1,927 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.sip_trunk_create_request_allowed_ips_item import SipTrunkCreateRequestAllowedIpsItem +from ..types.sip_trunk_create_request_host_request import SipTrunkCreateRequestHostRequest +from ..types.sip_trunk_list_response import SipTrunkListResponse +from ..types.sip_trunk_response import SipTrunkResponse +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawSipTrunksClient, RawSipTrunksClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SipTrunksClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSipTrunksClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSipTrunksClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSipTrunksClient + """ + return self._raw_client + + def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkListResponse: + """ + Returns a paginated list of SIP trunks for the authenticated account. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkListResponse + Returns a paginated list of SIP trunks. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sip_trunks.list() + """ + _response = self._raw_client.list(page=page, per_page=per_page, request_options=request_options) + return _response.data + + def create( + self, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkResponse: + """ + Creates a SIP trunk for routing inbound and outbound calls. Returns the trunk with its generated `access_token`. + + Parameters + ---------- + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the created SIP trunk. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sip_trunks.create( + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, + ) + """ + _response = self._raw_client.create( + label=label, + password=password, + callerid=callerid, + ip_restrict=ip_restrict, + didinfo_enabled=didinfo_enabled, + call_restrict=call_restrict, + cost_limit=cost_limit, + channels_restrict=channels_restrict, + rewrite_enabled=rewrite_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + host_request=host_request, + multiple_numbers=multiple_numbers, + allowed_ips=allowed_ips, + call_limit=call_limit, + max_call_cost=max_call_cost, + max_channels=max_channels, + rewrite_prefix=rewrite_prefix, + rewrite_cond=rewrite_cond, + call_recording_enabled=call_recording_enabled, + machine_detection_enabled=machine_detection_enabled, + encrypted_media=encrypted_media, + request_options=request_options, + ) + return _response.data + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SipTrunkResponse: + """ + Returns the SIP trunk identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the SIP trunk. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sip_trunks.get( + id=3107, + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def update( + self, + id: int, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkResponse: + """ + Replaces the configuration of the SIP trunk identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the updated SIP trunk. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sip_trunks.update( + id=3107, + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, + ) + """ + _response = self._raw_client.update( + id, + label=label, + password=password, + callerid=callerid, + ip_restrict=ip_restrict, + didinfo_enabled=didinfo_enabled, + call_restrict=call_restrict, + cost_limit=cost_limit, + channels_restrict=channels_restrict, + rewrite_enabled=rewrite_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + host_request=host_request, + multiple_numbers=multiple_numbers, + allowed_ips=allowed_ips, + call_limit=call_limit, + max_call_cost=max_call_cost, + max_channels=max_channels, + rewrite_prefix=rewrite_prefix, + rewrite_cond=rewrite_cond, + call_recording_enabled=call_recording_enabled, + machine_detection_enabled=machine_detection_enabled, + encrypted_media=encrypted_media, + request_options=request_options, + ) + return _response.data + + def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the SIP trunk identified by `id`. Deletion is permanent and stops call routing through the trunk. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The SIP trunk is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sip_trunks.delete( + id=3107, + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncSipTrunksClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSipTrunksClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSipTrunksClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSipTrunksClient + """ + return self._raw_client + + async def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkListResponse: + """ + Returns a paginated list of SIP trunks for the authenticated account. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkListResponse + Returns a paginated list of SIP trunks. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sip_trunks.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(page=page, per_page=per_page, request_options=request_options) + return _response.data + + async def create( + self, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkResponse: + """ + Creates a SIP trunk for routing inbound and outbound calls. Returns the trunk with its generated `access_token`. + + Parameters + ---------- + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the created SIP trunk. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sip_trunks.create( + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + label=label, + password=password, + callerid=callerid, + ip_restrict=ip_restrict, + didinfo_enabled=didinfo_enabled, + call_restrict=call_restrict, + cost_limit=cost_limit, + channels_restrict=channels_restrict, + rewrite_enabled=rewrite_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + host_request=host_request, + multiple_numbers=multiple_numbers, + allowed_ips=allowed_ips, + call_limit=call_limit, + max_call_cost=max_call_cost, + max_channels=max_channels, + rewrite_prefix=rewrite_prefix, + rewrite_cond=rewrite_cond, + call_recording_enabled=call_recording_enabled, + machine_detection_enabled=machine_detection_enabled, + encrypted_media=encrypted_media, + request_options=request_options, + ) + return _response.data + + async def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SipTrunkResponse: + """ + Returns the SIP trunk identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the SIP trunk. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sip_trunks.get( + id=3107, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def update( + self, + id: int, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SipTrunkResponse: + """ + Replaces the configuration of the SIP trunk identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SipTrunkResponse + Returns the updated SIP trunk. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sip_trunks.update( + id=3107, + label="My trunk", + password="4r=h;EaCB85QNtr2", + callerid="13132847320", + ip_restrict=False, + didinfo_enabled=True, + call_restrict=True, + cost_limit=True, + channels_restrict=False, + rewrite_enabled=True, + transcription_enabled=True, + transcription_threshold=10, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, + label=label, + password=password, + callerid=callerid, + ip_restrict=ip_restrict, + didinfo_enabled=didinfo_enabled, + call_restrict=call_restrict, + cost_limit=cost_limit, + channels_restrict=channels_restrict, + rewrite_enabled=rewrite_enabled, + transcription_enabled=transcription_enabled, + transcription_threshold=transcription_threshold, + host_request=host_request, + multiple_numbers=multiple_numbers, + allowed_ips=allowed_ips, + call_limit=call_limit, + max_call_cost=max_call_cost, + max_channels=max_channels, + rewrite_prefix=rewrite_prefix, + rewrite_cond=rewrite_cond, + call_recording_enabled=call_recording_enabled, + machine_detection_enabled=machine_detection_enabled, + encrypted_media=encrypted_media, + request_options=request_options, + ) + return _response.data + + async def delete(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the SIP trunk identified by `id`. Deletion is permanent and stops call routing through the trunk. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The SIP trunk is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sip_trunks.delete( + id=3107, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/src/wavix/sip_trunks/raw_client.py b/src/wavix/sip_trunks/raw_client.py new file mode 100644 index 0000000..b8b2b37 --- /dev/null +++ b/src/wavix/sip_trunks/raw_client.py @@ -0,0 +1,1324 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.sip_trunk_create_request_allowed_ips_item import SipTrunkCreateRequestAllowedIpsItem +from ..types.sip_trunk_create_request_host_request import SipTrunkCreateRequestHostRequest +from ..types.sip_trunk_list_response import SipTrunkListResponse +from ..types.sip_trunk_response import SipTrunkResponse +from ..types.success_response import SuccessResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSipTrunksClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SipTrunkListResponse]: + """ + Returns a paginated list of SIP trunks for the authenticated account. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SipTrunkListResponse] + Returns a paginated list of SIP trunks. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/trunks", + method="GET", + params={ + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkListResponse, + parse_obj_as( + type_=SipTrunkListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SipTrunkResponse]: + """ + Creates a SIP trunk for routing inbound and outbound calls. Returns the trunk with its generated `access_token`. + + Parameters + ---------- + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SipTrunkResponse] + Returns the created SIP trunk. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/trunks", + method="POST", + json={ + "label": label, + "password": password, + "host_request": convert_and_respect_annotation_metadata( + object_=host_request, annotation=SipTrunkCreateRequestHostRequest, direction="write" + ), + "callerid": callerid, + "multiple_numbers": multiple_numbers, + "ip_restrict": ip_restrict, + "allowed_ips": convert_and_respect_annotation_metadata( + object_=allowed_ips, + annotation=typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]], + direction="write", + ), + "didinfo_enabled": didinfo_enabled, + "call_restrict": call_restrict, + "call_limit": call_limit, + "cost_limit": cost_limit, + "max_call_cost": max_call_cost, + "channels_restrict": channels_restrict, + "max_channels": max_channels, + "rewrite_enabled": rewrite_enabled, + "rewrite_prefix": rewrite_prefix, + "rewrite_cond": rewrite_cond, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "machine_detection_enabled": machine_detection_enabled, + "encrypted_media": encrypted_media, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SipTrunkResponse]: + """ + Returns the SIP trunk identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SipTrunkResponse] + Returns the SIP trunk. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + id: int, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SipTrunkResponse]: + """ + Replaces the configuration of the SIP trunk identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SipTrunkResponse] + Returns the updated SIP trunk. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="PUT", + json={ + "label": label, + "password": password, + "host_request": convert_and_respect_annotation_metadata( + object_=host_request, annotation=SipTrunkCreateRequestHostRequest, direction="write" + ), + "callerid": callerid, + "multiple_numbers": multiple_numbers, + "ip_restrict": ip_restrict, + "allowed_ips": convert_and_respect_annotation_metadata( + object_=allowed_ips, + annotation=typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]], + direction="write", + ), + "didinfo_enabled": didinfo_enabled, + "call_restrict": call_restrict, + "call_limit": call_limit, + "cost_limit": cost_limit, + "max_call_cost": max_call_cost, + "channels_restrict": channels_restrict, + "max_channels": max_channels, + "rewrite_enabled": rewrite_enabled, + "rewrite_prefix": rewrite_prefix, + "rewrite_cond": rewrite_cond, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "machine_detection_enabled": machine_detection_enabled, + "encrypted_media": encrypted_media, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Deletes the SIP trunk identified by `id`. Deletion is permanent and stops call routing through the trunk. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The SIP trunk is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSipTrunksClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SipTrunkListResponse]: + """ + Returns a paginated list of SIP trunks for the authenticated account. + + Parameters + ---------- + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SipTrunkListResponse] + Returns a paginated list of SIP trunks. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/trunks", + method="GET", + params={ + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkListResponse, + parse_obj_as( + type_=SipTrunkListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SipTrunkResponse]: + """ + Creates a SIP trunk for routing inbound and outbound calls. Returns the trunk with its generated `access_token`. + + Parameters + ---------- + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SipTrunkResponse] + Returns the created SIP trunk. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/trunks", + method="POST", + json={ + "label": label, + "password": password, + "host_request": convert_and_respect_annotation_metadata( + object_=host_request, annotation=SipTrunkCreateRequestHostRequest, direction="write" + ), + "callerid": callerid, + "multiple_numbers": multiple_numbers, + "ip_restrict": ip_restrict, + "allowed_ips": convert_and_respect_annotation_metadata( + object_=allowed_ips, + annotation=typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]], + direction="write", + ), + "didinfo_enabled": didinfo_enabled, + "call_restrict": call_restrict, + "call_limit": call_limit, + "cost_limit": cost_limit, + "max_call_cost": max_call_cost, + "channels_restrict": channels_restrict, + "max_channels": max_channels, + "rewrite_enabled": rewrite_enabled, + "rewrite_prefix": rewrite_prefix, + "rewrite_cond": rewrite_cond, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "machine_detection_enabled": machine_detection_enabled, + "encrypted_media": encrypted_media, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SipTrunkResponse]: + """ + Returns the SIP trunk identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SipTrunkResponse] + Returns the SIP trunk. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + id: int, + *, + label: str, + password: str, + callerid: str, + ip_restrict: bool, + didinfo_enabled: bool, + call_restrict: bool, + cost_limit: bool, + channels_restrict: bool, + rewrite_enabled: bool, + transcription_enabled: bool, + transcription_threshold: int, + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = OMIT, + multiple_numbers: typing.Optional[bool] = OMIT, + allowed_ips: typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] = OMIT, + call_limit: typing.Optional[int] = OMIT, + max_call_cost: typing.Optional[float] = OMIT, + max_channels: typing.Optional[int] = OMIT, + rewrite_prefix: typing.Optional[str] = OMIT, + rewrite_cond: typing.Optional[str] = OMIT, + call_recording_enabled: typing.Optional[bool] = OMIT, + machine_detection_enabled: typing.Optional[bool] = OMIT, + encrypted_media: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SipTrunkResponse]: + """ + Replaces the configuration of the SIP trunk identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + label : str + User-defined name of the SIP trunk + + password : str + Password set for the SIP trunk. A strong password helps keep the trunk secure. + + callerid : str + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + + ip_restrict : bool + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + + didinfo_enabled : bool + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + + call_restrict : bool + Indicates whether a maximum call duration limit is enforced for the SIP trunk + + cost_limit : bool + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + + channels_restrict : bool + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + + rewrite_enabled : bool + Indicates whether a custom dial plan is activated for the SIP trunk + + transcription_enabled : bool + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + transcription_threshold : int + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + + host_request : typing.Optional[SipTrunkCreateRequestHostRequest] + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + + multiple_numbers : typing.Optional[bool] + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + + allowed_ips : typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]] + A list of public static IP addresses allowed to register with the SIP trunk + + call_limit : typing.Optional[int] + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + + max_call_cost : typing.Optional[float] + Maximum cost for an outbound call, in USD + + max_channels : typing.Optional[int] + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + + rewrite_prefix : typing.Optional[str] + Digits to automatically prepend to each dialed phone number + + rewrite_cond : typing.Optional[str] + Number of leading digits to automatically remove from each dialed phone number + + call_recording_enabled : typing.Optional[bool] + Indicates whether outbound call recording is enabled for the SIP trunk + + machine_detection_enabled : typing.Optional[bool] + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + + encrypted_media : typing.Optional[bool] + Indicates whether SRTP media encryption is enabled for the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SipTrunkResponse] + Returns the updated SIP trunk. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="PUT", + json={ + "label": label, + "password": password, + "host_request": convert_and_respect_annotation_metadata( + object_=host_request, annotation=SipTrunkCreateRequestHostRequest, direction="write" + ), + "callerid": callerid, + "multiple_numbers": multiple_numbers, + "ip_restrict": ip_restrict, + "allowed_ips": convert_and_respect_annotation_metadata( + object_=allowed_ips, + annotation=typing.Optional[typing.Sequence[SipTrunkCreateRequestAllowedIpsItem]], + direction="write", + ), + "didinfo_enabled": didinfo_enabled, + "call_restrict": call_restrict, + "call_limit": call_limit, + "cost_limit": cost_limit, + "max_call_cost": max_call_cost, + "channels_restrict": channels_restrict, + "max_channels": max_channels, + "rewrite_enabled": rewrite_enabled, + "rewrite_prefix": rewrite_prefix, + "rewrite_cond": rewrite_cond, + "call_recording_enabled": call_recording_enabled, + "transcription_enabled": transcription_enabled, + "transcription_threshold": transcription_threshold, + "machine_detection_enabled": machine_detection_enabled, + "encrypted_media": encrypted_media, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SipTrunkResponse, + parse_obj_as( + type_=SipTrunkResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Deletes the SIP trunk identified by `id`. Deletion is permanent and stops call routing through the trunk. + + Parameters + ---------- + id : int + The unique ID of the SIP trunk. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The SIP trunk is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/trunks/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sms_and_mms/__init__.py b/src/wavix/sms_and_mms/__init__.py new file mode 100644 index 0000000..1910a96 --- /dev/null +++ b/src/wavix/sms_and_mms/__init__.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import messages, opt_outs, sender_ids + from .messages import ( + GetMessagesResponse, + ListAllMessagesRequestMessageType, + ListMessagesRequestMessageType, + ListMessagesResponse, + ) + from .opt_outs import CreateOptOutsResponse + from .sender_ids import DeleteSenderIdsResponse, SenderIdCreateRequestMonthlyVolume, SenderIdCreateRequestUsecase +_dynamic_imports: typing.Dict[str, str] = { + "CreateOptOutsResponse": ".opt_outs", + "DeleteSenderIdsResponse": ".sender_ids", + "GetMessagesResponse": ".messages", + "ListAllMessagesRequestMessageType": ".messages", + "ListMessagesRequestMessageType": ".messages", + "ListMessagesResponse": ".messages", + "SenderIdCreateRequestMonthlyVolume": ".sender_ids", + "SenderIdCreateRequestUsecase": ".sender_ids", + "messages": ".messages", + "opt_outs": ".opt_outs", + "sender_ids": ".sender_ids", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateOptOutsResponse", + "DeleteSenderIdsResponse", + "GetMessagesResponse", + "ListAllMessagesRequestMessageType", + "ListMessagesRequestMessageType", + "ListMessagesResponse", + "SenderIdCreateRequestMonthlyVolume", + "SenderIdCreateRequestUsecase", + "messages", + "opt_outs", + "sender_ids", +] diff --git a/src/wavix/sms_and_mms/client.py b/src/wavix/sms_and_mms/client.py new file mode 100644 index 0000000..49b6842 --- /dev/null +++ b/src/wavix/sms_and_mms/client.py @@ -0,0 +1,101 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawSmsAndMmsClient, RawSmsAndMmsClient + +if typing.TYPE_CHECKING: + from .messages.client import AsyncMessagesClient, MessagesClient + from .opt_outs.client import AsyncOptOutsClient, OptOutsClient + from .sender_ids.client import AsyncSenderIdsClient, SenderIdsClient + + +class SmsAndMmsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSmsAndMmsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._sender_ids: typing.Optional[SenderIdsClient] = None + self._opt_outs: typing.Optional[OptOutsClient] = None + self._messages: typing.Optional[MessagesClient] = None + + @property + def with_raw_response(self) -> RawSmsAndMmsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSmsAndMmsClient + """ + return self._raw_client + + @property + def sender_ids(self): + if self._sender_ids is None: + from .sender_ids.client import SenderIdsClient # noqa: E402 + + self._sender_ids = SenderIdsClient(client_wrapper=self._client_wrapper) + return self._sender_ids + + @property + def opt_outs(self): + if self._opt_outs is None: + from .opt_outs.client import OptOutsClient # noqa: E402 + + self._opt_outs = OptOutsClient(client_wrapper=self._client_wrapper) + return self._opt_outs + + @property + def messages(self): + if self._messages is None: + from .messages.client import MessagesClient # noqa: E402 + + self._messages = MessagesClient(client_wrapper=self._client_wrapper) + return self._messages + + +class AsyncSmsAndMmsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSmsAndMmsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._sender_ids: typing.Optional[AsyncSenderIdsClient] = None + self._opt_outs: typing.Optional[AsyncOptOutsClient] = None + self._messages: typing.Optional[AsyncMessagesClient] = None + + @property + def with_raw_response(self) -> AsyncRawSmsAndMmsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSmsAndMmsClient + """ + return self._raw_client + + @property + def sender_ids(self): + if self._sender_ids is None: + from .sender_ids.client import AsyncSenderIdsClient # noqa: E402 + + self._sender_ids = AsyncSenderIdsClient(client_wrapper=self._client_wrapper) + return self._sender_ids + + @property + def opt_outs(self): + if self._opt_outs is None: + from .opt_outs.client import AsyncOptOutsClient # noqa: E402 + + self._opt_outs = AsyncOptOutsClient(client_wrapper=self._client_wrapper) + return self._opt_outs + + @property + def messages(self): + if self._messages is None: + from .messages.client import AsyncMessagesClient # noqa: E402 + + self._messages = AsyncMessagesClient(client_wrapper=self._client_wrapper) + return self._messages diff --git a/src/wavix/sms_and_mms/messages/__init__.py b/src/wavix/sms_and_mms/messages/__init__.py new file mode 100644 index 0000000..999b097 --- /dev/null +++ b/src/wavix/sms_and_mms/messages/__init__.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + GetMessagesResponse, + ListAllMessagesRequestMessageType, + ListMessagesRequestMessageType, + ListMessagesResponse, + ) +_dynamic_imports: typing.Dict[str, str] = { + "GetMessagesResponse": ".types", + "ListAllMessagesRequestMessageType": ".types", + "ListMessagesRequestMessageType": ".types", + "ListMessagesResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetMessagesResponse", + "ListAllMessagesRequestMessageType", + "ListMessagesRequestMessageType", + "ListMessagesResponse", +] diff --git a/src/wavix/sms_and_mms/messages/client.py b/src/wavix/sms_and_mms/messages/client.py new file mode 100644 index 0000000..d68e0dc --- /dev/null +++ b/src/wavix/sms_and_mms/messages/client.py @@ -0,0 +1,625 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.message_body import MessageBody +from ...types.message_delivery_status import MessageDeliveryStatus +from ...types.send_messages_response import SendMessagesResponse +from .raw_client import AsyncRawMessagesClient, RawMessagesClient +from .types.get_messages_response import GetMessagesResponse +from .types.list_all_messages_request_message_type import ListAllMessagesRequestMessageType +from .types.list_messages_request_message_type import ListMessagesRequestMessageType +from .types.list_messages_response import ListMessagesResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class MessagesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawMessagesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawMessagesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawMessagesClient + """ + return self._raw_client + + def list( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListMessagesRequestMessageType] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListMessagesResponse: + """ + Returns a paginated list of SMS and MMS messages for the authenticated account, filtered by direction, date, and other criteria. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this date, in `YYYY-MM-DD` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this date, in `YYYY-MM-DD` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, an SMS-enabled number on the Wavix platform. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListMessagesResponse + Returns a paginated list of messages. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.messages.list( + sent_after="2023-04-10", + sent_before="2023-04-13", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", + page=2, + per_page=50, + ) + """ + _response = self._raw_client.list( + type=type, + sent_after=sent_after, + sent_before=sent_before, + from_=from_, + to=to, + status=status, + tag=tag, + message_type=message_type, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def send( + self, + *, + from_: str, + to: str, + message_body: MessageBody, + callback_url: typing.Optional[str] = OMIT, + validity: typing.Optional[int] = OMIT, + tag: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SendMessagesResponse: + """ + Sends an SMS or MMS message. MMS is supported for U.S. numbers only. Track delivery using the returned `message_id` and the message status callback. + **Rate limit**: 20 messages per phone number in 24 hours. + + Parameters + ---------- + from_ : str + Sender ID. Numeric or alphanumeric. + + to : str + Recipient phone number. + + message_body : MessageBody + + callback_url : typing.Optional[str] + Callback URL for delivery reports. + + validity : typing.Optional[int] + Message validity period in seconds. Delivery attempts stop after this period expires. + + tag : typing.Optional[str] + Tag to group messages, such as for a specific campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SendMessagesResponse + Returns the submitted message with its `message_id` and initial status. + + Examples + -------- + from wavix import MessageBody, Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.messages.send( + from_="Wavix", + to="+447537151866", + message_body=MessageBody( + text="Hi there, this is a message from Wavix", + ), + callback_url="https://you-site.com/webhook", + validity=3600, + tag="Fall sale", + ) + """ + _response = self._raw_client.send( + from_=from_, + to=to, + message_body=message_body, + callback_url=callback_url, + validity=validity, + tag=tag, + request_options=request_options, + ) + return _response.data + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GetMessagesResponse: + """ + Returns the SMS or MMS message identified by `id`, including its delivery status and content. + + Parameters + ---------- + id : str + The unique ID of the message. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMessagesResponse + Returns the message. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.messages.get( + id="3a525ca2-6909-4c72-9399-905adf7f3a74", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def list_all( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListAllMessagesRequestMessageType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> str: + """ + Streams matching SMS and MMS messages as newline-delimited JSON (NDJSON), one message per line, for bulk export. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, the SMS-enabled number that received the message. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListAllMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + Returns an NDJSON stream of messages, one per line. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.messages.list_all( + sent_after="2023-04-10T00:00:00", + sent_before="2023-04-13T23:59:59", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", + ) + """ + _response = self._raw_client.list_all( + type=type, + sent_after=sent_after, + sent_before=sent_before, + from_=from_, + to=to, + status=status, + tag=tag, + message_type=message_type, + request_options=request_options, + ) + return _response.data + + +class AsyncMessagesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawMessagesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawMessagesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawMessagesClient + """ + return self._raw_client + + async def list( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListMessagesRequestMessageType] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListMessagesResponse: + """ + Returns a paginated list of SMS and MMS messages for the authenticated account, filtered by direction, date, and other criteria. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this date, in `YYYY-MM-DD` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this date, in `YYYY-MM-DD` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, an SMS-enabled number on the Wavix platform. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListMessagesResponse + Returns a paginated list of messages. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.messages.list( + sent_after="2023-04-10", + sent_before="2023-04-13", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", + page=2, + per_page=50, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + type=type, + sent_after=sent_after, + sent_before=sent_before, + from_=from_, + to=to, + status=status, + tag=tag, + message_type=message_type, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def send( + self, + *, + from_: str, + to: str, + message_body: MessageBody, + callback_url: typing.Optional[str] = OMIT, + validity: typing.Optional[int] = OMIT, + tag: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SendMessagesResponse: + """ + Sends an SMS or MMS message. MMS is supported for U.S. numbers only. Track delivery using the returned `message_id` and the message status callback. + **Rate limit**: 20 messages per phone number in 24 hours. + + Parameters + ---------- + from_ : str + Sender ID. Numeric or alphanumeric. + + to : str + Recipient phone number. + + message_body : MessageBody + + callback_url : typing.Optional[str] + Callback URL for delivery reports. + + validity : typing.Optional[int] + Message validity period in seconds. Delivery attempts stop after this period expires. + + tag : typing.Optional[str] + Tag to group messages, such as for a specific campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SendMessagesResponse + Returns the submitted message with its `message_id` and initial status. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix, MessageBody + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.messages.send( + from_="Wavix", + to="+447537151866", + message_body=MessageBody( + text="Hi there, this is a message from Wavix", + ), + callback_url="https://you-site.com/webhook", + validity=3600, + tag="Fall sale", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.send( + from_=from_, + to=to, + message_body=message_body, + callback_url=callback_url, + validity=validity, + tag=tag, + request_options=request_options, + ) + return _response.data + + async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GetMessagesResponse: + """ + Returns the SMS or MMS message identified by `id`, including its delivery status and content. + + Parameters + ---------- + id : str + The unique ID of the message. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetMessagesResponse + Returns the message. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.messages.get( + id="3a525ca2-6909-4c72-9399-905adf7f3a74", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def list_all( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListAllMessagesRequestMessageType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> str: + """ + Streams matching SMS and MMS messages as newline-delimited JSON (NDJSON), one message per line, for bulk export. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, the SMS-enabled number that received the message. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListAllMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + Returns an NDJSON stream of messages, one per line. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.messages.list_all( + sent_after="2023-04-10T00:00:00", + sent_before="2023-04-13T23:59:59", + type="outbound", + from_="15072429497", + to="16419252149", + tag="campaignX", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_all( + type=type, + sent_after=sent_after, + sent_before=sent_before, + from_=from_, + to=to, + status=status, + tag=tag, + message_type=message_type, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/sms_and_mms/messages/raw_client.py b/src/wavix/sms_and_mms/messages/raw_client.py new file mode 100644 index 0000000..2b73f29 --- /dev/null +++ b/src/wavix/sms_and_mms/messages/raw_client.py @@ -0,0 +1,793 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.message_body import MessageBody +from ...types.message_delivery_status import MessageDeliveryStatus +from ...types.send_messages_response import SendMessagesResponse +from .types.get_messages_response import GetMessagesResponse +from .types.list_all_messages_request_message_type import ListAllMessagesRequestMessageType +from .types.list_messages_request_message_type import ListMessagesRequestMessageType +from .types.list_messages_response import ListMessagesResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawMessagesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListMessagesRequestMessageType] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListMessagesResponse]: + """ + Returns a paginated list of SMS and MMS messages for the authenticated account, filtered by direction, date, and other criteria. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this date, in `YYYY-MM-DD` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this date, in `YYYY-MM-DD` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, an SMS-enabled number on the Wavix platform. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListMessagesResponse] + Returns a paginated list of messages. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages", + method="GET", + params={ + "sent_after": sent_after, + "sent_before": sent_before, + "type": type, + "from": from_, + "to": to, + "status": status, + "tag": tag, + "message_type": message_type, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListMessagesResponse, + parse_obj_as( + type_=ListMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def send( + self, + *, + from_: str, + to: str, + message_body: MessageBody, + callback_url: typing.Optional[str] = OMIT, + validity: typing.Optional[int] = OMIT, + tag: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SendMessagesResponse]: + """ + Sends an SMS or MMS message. MMS is supported for U.S. numbers only. Track delivery using the returned `message_id` and the message status callback. + **Rate limit**: 20 messages per phone number in 24 hours. + + Parameters + ---------- + from_ : str + Sender ID. Numeric or alphanumeric. + + to : str + Recipient phone number. + + message_body : MessageBody + + callback_url : typing.Optional[str] + Callback URL for delivery reports. + + validity : typing.Optional[int] + Message validity period in seconds. Delivery attempts stop after this period expires. + + tag : typing.Optional[str] + Tag to group messages, such as for a specific campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SendMessagesResponse] + Returns the submitted message with its `message_id` and initial status. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages", + method="POST", + json={ + "from": from_, + "to": to, + "message_body": convert_and_respect_annotation_metadata( + object_=message_body, annotation=MessageBody, direction="write" + ), + "callback_url": callback_url, + "validity": validity, + "tag": tag, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SendMessagesResponse, + parse_obj_as( + type_=SendMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[GetMessagesResponse]: + """ + Returns the SMS or MMS message identified by `id`, including its delivery status and content. + + Parameters + ---------- + id : str + The unique ID of the message. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetMessagesResponse] + Returns the message. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/messages/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMessagesResponse, + parse_obj_as( + type_=GetMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_all( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListAllMessagesRequestMessageType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[str]: + """ + Streams matching SMS and MMS messages as newline-delimited JSON (NDJSON), one message per line, for bulk export. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, the SMS-enabled number that received the message. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListAllMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[str] + Returns an NDJSON stream of messages, one per line. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages/all", + method="GET", + params={ + "sent_after": sent_after, + "sent_before": sent_before, + "type": type, + "from": from_, + "to": to, + "status": status, + "tag": tag, + "message_type": message_type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + str, + parse_obj_as( + type_=str, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawMessagesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListMessagesRequestMessageType] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListMessagesResponse]: + """ + Returns a paginated list of SMS and MMS messages for the authenticated account, filtered by direction, date, and other criteria. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this date, in `YYYY-MM-DD` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this date, in `YYYY-MM-DD` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, an SMS-enabled number on the Wavix platform. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListMessagesResponse] + Returns a paginated list of messages. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages", + method="GET", + params={ + "sent_after": sent_after, + "sent_before": sent_before, + "type": type, + "from": from_, + "to": to, + "status": status, + "tag": tag, + "message_type": message_type, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListMessagesResponse, + parse_obj_as( + type_=ListMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def send( + self, + *, + from_: str, + to: str, + message_body: MessageBody, + callback_url: typing.Optional[str] = OMIT, + validity: typing.Optional[int] = OMIT, + tag: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SendMessagesResponse]: + """ + Sends an SMS or MMS message. MMS is supported for U.S. numbers only. Track delivery using the returned `message_id` and the message status callback. + **Rate limit**: 20 messages per phone number in 24 hours. + + Parameters + ---------- + from_ : str + Sender ID. Numeric or alphanumeric. + + to : str + Recipient phone number. + + message_body : MessageBody + + callback_url : typing.Optional[str] + Callback URL for delivery reports. + + validity : typing.Optional[int] + Message validity period in seconds. Delivery attempts stop after this period expires. + + tag : typing.Optional[str] + Tag to group messages, such as for a specific campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SendMessagesResponse] + Returns the submitted message with its `message_id` and initial status. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages", + method="POST", + json={ + "from": from_, + "to": to, + "message_body": convert_and_respect_annotation_metadata( + object_=message_body, annotation=MessageBody, direction="write" + ), + "callback_url": callback_url, + "validity": validity, + "tag": tag, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SendMessagesResponse, + parse_obj_as( + type_=SendMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetMessagesResponse]: + """ + Returns the SMS or MMS message identified by `id`, including its delivery status and content. + + Parameters + ---------- + id : str + The unique ID of the message. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetMessagesResponse] + Returns the message. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/messages/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetMessagesResponse, + parse_obj_as( + type_=GetMessagesResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_all( + self, + *, + type: str, + sent_after: typing.Optional[str] = None, + sent_before: typing.Optional[str] = None, + from_: typing.Optional[str] = None, + to: typing.Optional[str] = None, + status: typing.Optional[MessageDeliveryStatus] = None, + tag: typing.Optional[str] = None, + message_type: typing.Optional[ListAllMessagesRequestMessageType] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[str]: + """ + Streams matching SMS and MMS messages as newline-delimited JSON (NDJSON), one message per line, for bulk export. + + Parameters + ---------- + type : str + Filters messages by direction. One of `inbound` (messages received by the account) or `outbound` (messages sent by the account). + + sent_after : typing.Optional[str] + Returns messages sent on or after this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + sent_before : typing.Optional[str] + Returns messages sent on or before this timestamp, in `YYYY-MM-DDTHH:MM:SS` format. + + from_ : typing.Optional[str] + Filters by message sender. For `outbound` messages, the Sender ID used to send the message; for `inbound` messages, the originating phone number. + + to : typing.Optional[str] + Filters by message recipient. For `outbound` messages, the destination phone number; for `inbound` messages, the SMS-enabled number that received the message. + + status : typing.Optional[MessageDeliveryStatus] + Filters messages by delivery status. Accepts a `MessageDeliveryStatus` value. + + tag : typing.Optional[str] + Filters messages by `tag`. Supported for outbound messages only. + + message_type : typing.Optional[ListAllMessagesRequestMessageType] + Filters messages by type. One of `sms` (text message) or `mms` (multimedia message). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[str] + Returns an NDJSON stream of messages, one per line. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages/all", + method="GET", + params={ + "sent_after": sent_after, + "sent_before": sent_before, + "type": type, + "from": from_, + "to": to, + "status": status, + "tag": tag, + "message_type": message_type, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + str, + parse_obj_as( + type_=str, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sms_and_mms/messages/types/__init__.py b/src/wavix/sms_and_mms/messages/types/__init__.py new file mode 100644 index 0000000..0ca11f7 --- /dev/null +++ b/src/wavix/sms_and_mms/messages/types/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .get_messages_response import GetMessagesResponse + from .list_all_messages_request_message_type import ListAllMessagesRequestMessageType + from .list_messages_request_message_type import ListMessagesRequestMessageType + from .list_messages_response import ListMessagesResponse +_dynamic_imports: typing.Dict[str, str] = { + "GetMessagesResponse": ".get_messages_response", + "ListAllMessagesRequestMessageType": ".list_all_messages_request_message_type", + "ListMessagesRequestMessageType": ".list_messages_request_message_type", + "ListMessagesResponse": ".list_messages_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "GetMessagesResponse", + "ListAllMessagesRequestMessageType", + "ListMessagesRequestMessageType", + "ListMessagesResponse", +] diff --git a/src/wavix/sms_and_mms/messages/types/get_messages_response.py b/src/wavix/sms_and_mms/messages/types/get_messages_response.py new file mode 100644 index 0000000..6651bbe --- /dev/null +++ b/src/wavix/sms_and_mms/messages/types/get_messages_response.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....core.serialization import FieldMetadata +from ....types.message_body import MessageBody +from ....types.message_delivery_status import MessageDeliveryStatus + + +class GetMessagesResponse(UniversalBaseModel): + message_id: str = pydantic.Field() + """ + Message ID. + """ + + message_type: str = pydantic.Field() + """ + Message type. Possible values are `sms`, `mms`. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Sender ID.") + ] + to: str = pydantic.Field() + """ + Recipient phone number. + """ + + direction: str = pydantic.Field() + """ + Message direction. Possible values are `outbound`, `inbound`. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile country code. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile network code. + """ + + message_body: MessageBody + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Tag to group messages, such as for a specific campaign. + """ + + status: MessageDeliveryStatus + segments: int = pydantic.Field() + """ + Number of SMS segments. Always 1 for MMS. + """ + + charge: str = pydantic.Field() + """ + Total charge for the message in USD. + """ + + submitted_at: str = pydantic.Field() + """ + Date and time the message was accepted in ISO 8601 format. + """ + + sent_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was sent in ISO 8601 format. For mobile-terminated messages only. + """ + + delivered_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was delivered in ISO 8601 format. Refers to DLR reception for mobile-terminated messages + or webhook relay for mobile-originated messages. + """ + + error_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error message. + """ + + carrier_fees: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile carrier fees in USD. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sms_and_mms/messages/types/list_all_messages_request_message_type.py b/src/wavix/sms_and_mms/messages/types/list_all_messages_request_message_type.py new file mode 100644 index 0000000..1bf5390 --- /dev/null +++ b/src/wavix/sms_and_mms/messages/types/list_all_messages_request_message_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListAllMessagesRequestMessageType = typing.Union[typing.Literal["sms", "mms"], typing.Any] diff --git a/src/wavix/sms_and_mms/messages/types/list_messages_request_message_type.py b/src/wavix/sms_and_mms/messages/types/list_messages_request_message_type.py new file mode 100644 index 0000000..204373d --- /dev/null +++ b/src/wavix/sms_and_mms/messages/types/list_messages_request_message_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListMessagesRequestMessageType = typing.Union[typing.Literal["sms", "mms"], typing.Any] diff --git a/src/wavix/sms_and_mms/messages/types/list_messages_response.py b/src/wavix/sms_and_mms/messages/types/list_messages_response.py new file mode 100644 index 0000000..5da8279 --- /dev/null +++ b/src/wavix/sms_and_mms/messages/types/list_messages_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.message import Message +from ....types.pagination import Pagination + + +class ListMessagesResponse(UniversalBaseModel): + items: typing.List[Message] = pydantic.Field() + """ + Messages that match the request. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sms_and_mms/opt_outs/__init__.py b/src/wavix/sms_and_mms/opt_outs/__init__.py new file mode 100644 index 0000000..403e602 --- /dev/null +++ b/src/wavix/sms_and_mms/opt_outs/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateOptOutsResponse +_dynamic_imports: typing.Dict[str, str] = {"CreateOptOutsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateOptOutsResponse"] diff --git a/src/wavix/sms_and_mms/opt_outs/client.py b/src/wavix/sms_and_mms/opt_outs/client.py new file mode 100644 index 0000000..ae60a5a --- /dev/null +++ b/src/wavix/sms_and_mms/opt_outs/client.py @@ -0,0 +1,277 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.opt_out import OptOut +from ...types.opt_outs_list_response import OptOutsListResponse +from .raw_client import AsyncRawOptOutsClient, RawOptOutsClient +from .types.create_opt_outs_response import CreateOptOutsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class OptOutsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawOptOutsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawOptOutsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawOptOutsClient + """ + return self._raw_client + + def list( + self, + *, + sender_id: typing.Optional[str] = None, + campaign_id: typing.Optional[str] = None, + created_after: typing.Optional[dt.date] = None, + created_before: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> OptOutsListResponse: + """ + Returns a paginated list of phone numbers that have opted out of receiving messages from the authenticated account. + + Parameters + ---------- + sender_id : typing.Optional[str] + Filters opt-outs by the Sender ID they apply to. + + campaign_id : typing.Optional[str] + Filters opt-outs by the 10DLC campaign ID they apply to. + + created_after : typing.Optional[dt.date] + Returns opt-outs created on or after this date, in `YYYY-MM-DD` format. + + created_before : typing.Optional[dt.date] + Returns opt-outs created on or before this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Minimum `1`, default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`, maximum `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OptOutsListResponse + Returns a paginated list of opted-out phone numbers. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.opt_outs.list( + sender_id="MySender", + campaign_id="C123456", + created_after=datetime.date.fromisoformat( + "2024-01-01", + ), + created_before=datetime.date.fromisoformat( + "2024-12-31", + ), + ) + """ + _response = self._raw_client.list( + sender_id=sender_id, + campaign_id=campaign_id, + created_after=created_after, + created_before=created_before, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def create( + self, *, opt_out: OptOut, request_options: typing.Optional[RequestOptions] = None + ) -> CreateOptOutsResponse: + """ + Opts a phone number out of receiving messages from a Sender ID, a 10DLC campaign, or all outbound messages. + + Parameters + ---------- + opt_out : OptOut + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateOptOutsResponse + Returns a success confirmation. The opt-out is created. + + Examples + -------- + from wavix import OptOut, Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.opt_outs.create( + opt_out=OptOut( + number="16419252149", + sender_id="15072429497", + ), + ) + """ + _response = self._raw_client.create(opt_out=opt_out, request_options=request_options) + return _response.data + + +class AsyncOptOutsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawOptOutsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawOptOutsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawOptOutsClient + """ + return self._raw_client + + async def list( + self, + *, + sender_id: typing.Optional[str] = None, + campaign_id: typing.Optional[str] = None, + created_after: typing.Optional[dt.date] = None, + created_before: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> OptOutsListResponse: + """ + Returns a paginated list of phone numbers that have opted out of receiving messages from the authenticated account. + + Parameters + ---------- + sender_id : typing.Optional[str] + Filters opt-outs by the Sender ID they apply to. + + campaign_id : typing.Optional[str] + Filters opt-outs by the 10DLC campaign ID they apply to. + + created_after : typing.Optional[dt.date] + Returns opt-outs created on or after this date, in `YYYY-MM-DD` format. + + created_before : typing.Optional[dt.date] + Returns opt-outs created on or before this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Minimum `1`, default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`, maximum `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + OptOutsListResponse + Returns a paginated list of opted-out phone numbers. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.opt_outs.list( + sender_id="MySender", + campaign_id="C123456", + created_after=datetime.date.fromisoformat( + "2024-01-01", + ), + created_before=datetime.date.fromisoformat( + "2024-12-31", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + sender_id=sender_id, + campaign_id=campaign_id, + created_after=created_after, + created_before=created_before, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def create( + self, *, opt_out: OptOut, request_options: typing.Optional[RequestOptions] = None + ) -> CreateOptOutsResponse: + """ + Opts a phone number out of receiving messages from a Sender ID, a 10DLC campaign, or all outbound messages. + + Parameters + ---------- + opt_out : OptOut + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateOptOutsResponse + Returns a success confirmation. The opt-out is created. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix, OptOut + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.opt_outs.create( + opt_out=OptOut( + number="16419252149", + sender_id="15072429497", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create(opt_out=opt_out, request_options=request_options) + return _response.data diff --git a/src/wavix/sms_and_mms/opt_outs/raw_client.py b/src/wavix/sms_and_mms/opt_outs/raw_client.py new file mode 100644 index 0000000..7fcd538 --- /dev/null +++ b/src/wavix/sms_and_mms/opt_outs/raw_client.py @@ -0,0 +1,438 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.opt_out import OptOut +from ...types.opt_outs_list_response import OptOutsListResponse +from .types.create_opt_outs_response import CreateOptOutsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawOptOutsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + sender_id: typing.Optional[str] = None, + campaign_id: typing.Optional[str] = None, + created_after: typing.Optional[dt.date] = None, + created_before: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[OptOutsListResponse]: + """ + Returns a paginated list of phone numbers that have opted out of receiving messages from the authenticated account. + + Parameters + ---------- + sender_id : typing.Optional[str] + Filters opt-outs by the Sender ID they apply to. + + campaign_id : typing.Optional[str] + Filters opt-outs by the 10DLC campaign ID they apply to. + + created_after : typing.Optional[dt.date] + Returns opt-outs created on or after this date, in `YYYY-MM-DD` format. + + created_before : typing.Optional[dt.date] + Returns opt-outs created on or before this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Minimum `1`, default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`, maximum `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[OptOutsListResponse] + Returns a paginated list of opted-out phone numbers. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages/opt-outs", + method="GET", + params={ + "sender_id": sender_id, + "campaign_id": campaign_id, + "created_after": str(created_after) if created_after is not None else None, + "created_before": str(created_before) if created_before is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OptOutsListResponse, + parse_obj_as( + type_=OptOutsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, *, opt_out: OptOut, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CreateOptOutsResponse]: + """ + Opts a phone number out of receiving messages from a Sender ID, a 10DLC campaign, or all outbound messages. + + Parameters + ---------- + opt_out : OptOut + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateOptOutsResponse] + Returns a success confirmation. The opt-out is created. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages/opt-outs", + method="POST", + json={ + "opt_out": convert_and_respect_annotation_metadata( + object_=opt_out, annotation=OptOut, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateOptOutsResponse, + parse_obj_as( + type_=CreateOptOutsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawOptOutsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + sender_id: typing.Optional[str] = None, + campaign_id: typing.Optional[str] = None, + created_after: typing.Optional[dt.date] = None, + created_before: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[OptOutsListResponse]: + """ + Returns a paginated list of phone numbers that have opted out of receiving messages from the authenticated account. + + Parameters + ---------- + sender_id : typing.Optional[str] + Filters opt-outs by the Sender ID they apply to. + + campaign_id : typing.Optional[str] + Filters opt-outs by the 10DLC campaign ID they apply to. + + created_after : typing.Optional[dt.date] + Returns opt-outs created on or after this date, in `YYYY-MM-DD` format. + + created_before : typing.Optional[dt.date] + Returns opt-outs created on or before this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Minimum `1`, default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`, maximum `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[OptOutsListResponse] + Returns a paginated list of opted-out phone numbers. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages/opt-outs", + method="GET", + params={ + "sender_id": sender_id, + "campaign_id": campaign_id, + "created_after": str(created_after) if created_after is not None else None, + "created_before": str(created_before) if created_before is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + OptOutsListResponse, + parse_obj_as( + type_=OptOutsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, *, opt_out: OptOut, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreateOptOutsResponse]: + """ + Opts a phone number out of receiving messages from a Sender ID, a 10DLC campaign, or all outbound messages. + + Parameters + ---------- + opt_out : OptOut + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateOptOutsResponse] + Returns a success confirmation. The opt-out is created. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages/opt-outs", + method="POST", + json={ + "opt_out": convert_and_respect_annotation_metadata( + object_=opt_out, annotation=OptOut, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateOptOutsResponse, + parse_obj_as( + type_=CreateOptOutsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sms_and_mms/opt_outs/types/__init__.py b/src/wavix/sms_and_mms/opt_outs/types/__init__.py new file mode 100644 index 0000000..b744285 --- /dev/null +++ b/src/wavix/sms_and_mms/opt_outs/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_opt_outs_response import CreateOptOutsResponse +_dynamic_imports: typing.Dict[str, str] = {"CreateOptOutsResponse": ".create_opt_outs_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateOptOutsResponse"] diff --git a/src/wavix/sms_and_mms/opt_outs/types/create_opt_outs_response.py b/src/wavix/sms_and_mms/opt_outs/types/create_opt_outs_response.py new file mode 100644 index 0000000..08da10a --- /dev/null +++ b/src/wavix/sms_and_mms/opt_outs/types/create_opt_outs_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateOptOutsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sms_and_mms/raw_client.py b/src/wavix/sms_and_mms/raw_client.py new file mode 100644 index 0000000..927847d --- /dev/null +++ b/src/wavix/sms_and_mms/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawSmsAndMmsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawSmsAndMmsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/sms_and_mms/sender_ids/__init__.py b/src/wavix/sms_and_mms/sender_ids/__init__.py new file mode 100644 index 0000000..e8e21be --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import DeleteSenderIdsResponse, SenderIdCreateRequestMonthlyVolume, SenderIdCreateRequestUsecase +_dynamic_imports: typing.Dict[str, str] = { + "DeleteSenderIdsResponse": ".types", + "SenderIdCreateRequestMonthlyVolume": ".types", + "SenderIdCreateRequestUsecase": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteSenderIdsResponse", "SenderIdCreateRequestMonthlyVolume", "SenderIdCreateRequestUsecase"] diff --git a/src/wavix/sms_and_mms/sender_ids/client.py b/src/wavix/sms_and_mms/sender_ids/client.py new file mode 100644 index 0000000..7a102fb --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/client.py @@ -0,0 +1,391 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.sender_id_details import SenderIdDetails +from ...types.sender_id_list_response import SenderIdListResponse +from ...types.sender_id_response import SenderIdResponse +from ...types.sender_id_type import SenderIdType +from .raw_client import AsyncRawSenderIdsClient, RawSenderIdsClient +from .types.delete_sender_ids_response import DeleteSenderIdsResponse +from .types.sender_id_create_request_monthly_volume import SenderIdCreateRequestMonthlyVolume +from .types.sender_id_create_request_usecase import SenderIdCreateRequestUsecase + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SenderIdsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSenderIdsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSenderIdsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSenderIdsClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> SenderIdListResponse: + """ + Returns the Sender IDs registered for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdListResponse + Returns the list of Sender IDs. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.sender_ids.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + sender_id: str, + type: SenderIdType, + countries: typing.Sequence[str], + usecase: SenderIdCreateRequestUsecase, + monthly_volume: typing.Optional[SenderIdCreateRequestMonthlyVolume] = OMIT, + samples: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SenderIdDetails: + """ + Creates a Sender ID. Use the 10DLC API to create Sender IDs in the US. + + Parameters + ---------- + sender_id : str + Sender ID name. Can be either an alphanumeric string or a phone number. + + type : SenderIdType + + countries : typing.Sequence[str] + Two-letter ISO country codes where the Sender ID is allowlisted. + + usecase : SenderIdCreateRequestUsecase + Primary use case for the Sender ID. One of `transactional` (account or order notifications), `promo` (marketing and promotional messages), or `authentication` (one-time passcodes and verification codes). + + monthly_volume : typing.Optional[SenderIdCreateRequestMonthlyVolume] + Expected number of messages sent per month from the Sender ID. One of `1-1000`, `1001-20000`, `20001-50000`, `50001-100000`, or `More than 100000`. Each value is the message-count band for the month. + + samples : typing.Optional[typing.Sequence[str]] + Message samples. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdDetails + Returns the created Sender ID. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.sender_ids.create( + sender_id="Wavix", + type="numeric", + countries=["countries"], + usecase="transactional", + ) + """ + _response = self._raw_client.create( + sender_id=sender_id, + type=type, + countries=countries, + usecase=usecase, + monthly_volume=monthly_volume, + samples=samples, + request_options=request_options, + ) + return _response.data + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SenderIdResponse: + """ + Returns the Sender ID identified by `id`. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdResponse + Returns the Sender ID. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.sender_ids.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DeleteSenderIdsResponse: + """ + Deletes the Sender ID identified by `id`. Deletion is permanent. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteSenderIdsResponse + Returns a success confirmation. The Sender ID is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sms_and_mms.sender_ids.delete( + id="fc34ba88-1eee-476e-b09e-dae63dc441e0", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncSenderIdsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSenderIdsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSenderIdsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSenderIdsClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> SenderIdListResponse: + """ + Returns the Sender IDs registered for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdListResponse + Returns the list of Sender IDs. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.sender_ids.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + sender_id: str, + type: SenderIdType, + countries: typing.Sequence[str], + usecase: SenderIdCreateRequestUsecase, + monthly_volume: typing.Optional[SenderIdCreateRequestMonthlyVolume] = OMIT, + samples: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SenderIdDetails: + """ + Creates a Sender ID. Use the 10DLC API to create Sender IDs in the US. + + Parameters + ---------- + sender_id : str + Sender ID name. Can be either an alphanumeric string or a phone number. + + type : SenderIdType + + countries : typing.Sequence[str] + Two-letter ISO country codes where the Sender ID is allowlisted. + + usecase : SenderIdCreateRequestUsecase + Primary use case for the Sender ID. One of `transactional` (account or order notifications), `promo` (marketing and promotional messages), or `authentication` (one-time passcodes and verification codes). + + monthly_volume : typing.Optional[SenderIdCreateRequestMonthlyVolume] + Expected number of messages sent per month from the Sender ID. One of `1-1000`, `1001-20000`, `20001-50000`, `50001-100000`, or `More than 100000`. Each value is the message-count band for the month. + + samples : typing.Optional[typing.Sequence[str]] + Message samples. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdDetails + Returns the created Sender ID. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.sender_ids.create( + sender_id="Wavix", + type="numeric", + countries=["countries"], + usecase="transactional", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + sender_id=sender_id, + type=type, + countries=countries, + usecase=usecase, + monthly_volume=monthly_volume, + samples=samples, + request_options=request_options, + ) + return _response.data + + async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SenderIdResponse: + """ + Returns the Sender ID identified by `id`. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SenderIdResponse + Returns the Sender ID. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.sender_ids.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteSenderIdsResponse: + """ + Deletes the Sender ID identified by `id`. Deletion is permanent. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteSenderIdsResponse + Returns a success confirmation. The Sender ID is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sms_and_mms.sender_ids.delete( + id="fc34ba88-1eee-476e-b09e-dae63dc441e0", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/src/wavix/sms_and_mms/sender_ids/raw_client.py b/src/wavix/sms_and_mms/sender_ids/raw_client.py new file mode 100644 index 0000000..c338aa7 --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/raw_client.py @@ -0,0 +1,617 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.sender_id_details import SenderIdDetails +from ...types.sender_id_list_response import SenderIdListResponse +from ...types.sender_id_response import SenderIdResponse +from ...types.sender_id_type import SenderIdType +from .types.delete_sender_ids_response import DeleteSenderIdsResponse +from .types.sender_id_create_request_monthly_volume import SenderIdCreateRequestMonthlyVolume +from .types.sender_id_create_request_usecase import SenderIdCreateRequestUsecase +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSenderIdsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SenderIdListResponse]: + """ + Returns the Sender IDs registered for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SenderIdListResponse] + Returns the list of Sender IDs. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages/sender-ids", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdListResponse, + parse_obj_as( + type_=SenderIdListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + sender_id: str, + type: SenderIdType, + countries: typing.Sequence[str], + usecase: SenderIdCreateRequestUsecase, + monthly_volume: typing.Optional[SenderIdCreateRequestMonthlyVolume] = OMIT, + samples: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SenderIdDetails]: + """ + Creates a Sender ID. Use the 10DLC API to create Sender IDs in the US. + + Parameters + ---------- + sender_id : str + Sender ID name. Can be either an alphanumeric string or a phone number. + + type : SenderIdType + + countries : typing.Sequence[str] + Two-letter ISO country codes where the Sender ID is allowlisted. + + usecase : SenderIdCreateRequestUsecase + Primary use case for the Sender ID. One of `transactional` (account or order notifications), `promo` (marketing and promotional messages), or `authentication` (one-time passcodes and verification codes). + + monthly_volume : typing.Optional[SenderIdCreateRequestMonthlyVolume] + Expected number of messages sent per month from the Sender ID. One of `1-1000`, `1001-20000`, `20001-50000`, `50001-100000`, or `More than 100000`. Each value is the message-count band for the month. + + samples : typing.Optional[typing.Sequence[str]] + Message samples. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SenderIdDetails] + Returns the created Sender ID. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/messages/sender-ids", + method="POST", + json={ + "sender_id": sender_id, + "type": type, + "countries": countries, + "usecase": usecase, + "monthly_volume": monthly_volume, + "samples": samples, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdDetails, + parse_obj_as( + type_=SenderIdDetails, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SenderIdResponse]: + """ + Returns the Sender ID identified by `id`. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SenderIdResponse] + Returns the Sender ID. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/messages/sender-ids/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdResponse, + parse_obj_as( + type_=SenderIdResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteSenderIdsResponse]: + """ + Deletes the Sender ID identified by `id`. Deletion is permanent. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteSenderIdsResponse] + Returns a success confirmation. The Sender ID is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/messages/sender-ids/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteSenderIdsResponse, + parse_obj_as( + type_=DeleteSenderIdsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSenderIdsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SenderIdListResponse]: + """ + Returns the Sender IDs registered for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SenderIdListResponse] + Returns the list of Sender IDs. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages/sender-ids", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdListResponse, + parse_obj_as( + type_=SenderIdListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + sender_id: str, + type: SenderIdType, + countries: typing.Sequence[str], + usecase: SenderIdCreateRequestUsecase, + monthly_volume: typing.Optional[SenderIdCreateRequestMonthlyVolume] = OMIT, + samples: typing.Optional[typing.Sequence[str]] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SenderIdDetails]: + """ + Creates a Sender ID. Use the 10DLC API to create Sender IDs in the US. + + Parameters + ---------- + sender_id : str + Sender ID name. Can be either an alphanumeric string or a phone number. + + type : SenderIdType + + countries : typing.Sequence[str] + Two-letter ISO country codes where the Sender ID is allowlisted. + + usecase : SenderIdCreateRequestUsecase + Primary use case for the Sender ID. One of `transactional` (account or order notifications), `promo` (marketing and promotional messages), or `authentication` (one-time passcodes and verification codes). + + monthly_volume : typing.Optional[SenderIdCreateRequestMonthlyVolume] + Expected number of messages sent per month from the Sender ID. One of `1-1000`, `1001-20000`, `20001-50000`, `50001-100000`, or `More than 100000`. Each value is the message-count band for the month. + + samples : typing.Optional[typing.Sequence[str]] + Message samples. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SenderIdDetails] + Returns the created Sender ID. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/messages/sender-ids", + method="POST", + json={ + "sender_id": sender_id, + "type": type, + "countries": countries, + "usecase": usecase, + "monthly_volume": monthly_volume, + "samples": samples, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdDetails, + parse_obj_as( + type_=SenderIdDetails, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SenderIdResponse]: + """ + Returns the Sender ID identified by `id`. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SenderIdResponse] + Returns the Sender ID. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/messages/sender-ids/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SenderIdResponse, + parse_obj_as( + type_=SenderIdResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteSenderIdsResponse]: + """ + Deletes the Sender ID identified by `id`. Deletion is permanent. + + Parameters + ---------- + id : str + The unique ID of the Sender ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteSenderIdsResponse] + Returns a success confirmation. The Sender ID is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/messages/sender-ids/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteSenderIdsResponse, + parse_obj_as( + type_=DeleteSenderIdsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sms_and_mms/sender_ids/types/__init__.py b/src/wavix/sms_and_mms/sender_ids/types/__init__.py new file mode 100644 index 0000000..cb12fd2 --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .delete_sender_ids_response import DeleteSenderIdsResponse + from .sender_id_create_request_monthly_volume import SenderIdCreateRequestMonthlyVolume + from .sender_id_create_request_usecase import SenderIdCreateRequestUsecase +_dynamic_imports: typing.Dict[str, str] = { + "DeleteSenderIdsResponse": ".delete_sender_ids_response", + "SenderIdCreateRequestMonthlyVolume": ".sender_id_create_request_monthly_volume", + "SenderIdCreateRequestUsecase": ".sender_id_create_request_usecase", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteSenderIdsResponse", "SenderIdCreateRequestMonthlyVolume", "SenderIdCreateRequestUsecase"] diff --git a/src/wavix/sms_and_mms/sender_ids/types/delete_sender_ids_response.py b/src/wavix/sms_and_mms/sender_ids/types/delete_sender_ids_response.py new file mode 100644 index 0000000..365c8fb --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/types/delete_sender_ids_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteSenderIdsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_monthly_volume.py b/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_monthly_volume.py new file mode 100644 index 0000000..73c7828 --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_monthly_volume.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SenderIdCreateRequestMonthlyVolume = typing.Union[ + typing.Literal["1-1000", "1001-20000", "20001-50000", "50001-100000", "More than 100000"], typing.Any +] diff --git a/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_usecase.py b/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_usecase.py new file mode 100644 index 0000000..058ce31 --- /dev/null +++ b/src/wavix/sms_and_mms/sender_ids/types/sender_id_create_request_usecase.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SenderIdCreateRequestUsecase = typing.Union[typing.Literal["transactional", "promo", "authentication"], typing.Any] diff --git a/src/wavix/speech_analytics/__init__.py b/src/wavix/speech_analytics/__init__.py new file mode 100644 index 0000000..1a4f19a --- /dev/null +++ b/src/wavix/speech_analytics/__init__.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateSpeechAnalyticsResponse, + GetSpeechAnalyticsResponse, + GetSpeechAnalyticsResponseLanguage, + GetSpeechAnalyticsResponseStatus, + ) + from . import file +_dynamic_imports: typing.Dict[str, str] = { + "CreateSpeechAnalyticsResponse": ".types", + "GetSpeechAnalyticsResponse": ".types", + "GetSpeechAnalyticsResponseLanguage": ".types", + "GetSpeechAnalyticsResponseStatus": ".types", + "file": ".file", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateSpeechAnalyticsResponse", + "GetSpeechAnalyticsResponse", + "GetSpeechAnalyticsResponseLanguage", + "GetSpeechAnalyticsResponseStatus", + "file", +] diff --git a/src/wavix/speech_analytics/client.py b/src/wavix/speech_analytics/client.py new file mode 100644 index 0000000..2f55c65 --- /dev/null +++ b/src/wavix/speech_analytics/client.py @@ -0,0 +1,372 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from .. import core +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.success_response import SuccessResponse +from .raw_client import AsyncRawSpeechAnalyticsClient, RawSpeechAnalyticsClient +from .types.create_speech_analytics_response import CreateSpeechAnalyticsResponse +from .types.get_speech_analytics_response import GetSpeechAnalyticsResponse + +if typing.TYPE_CHECKING: + from .file.client import AsyncFileClient, FileClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SpeechAnalyticsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSpeechAnalyticsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._file: typing.Optional[FileClient] = None + + @property + def with_raw_response(self) -> RawSpeechAnalyticsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSpeechAnalyticsClient + """ + return self._raw_client + + def create( + self, + *, + file: core.File, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateSpeechAnalyticsResponse: + """ + Uploads an audio file for transcription. Transcription is asynchronous; Wavix sends a POST callback to `callback_url` when it completes, including the `request_id` returned by this request. + + Callback body: + ```json + { + "request_id": "e865ea07-25af-4fdd-876e-04b0d41d5ebd", + "status": "completed", + "error": null + } + ``` + + - `request_id`: ID of the transcription request. + - `status`: One of `completed` (transcription succeeded) or `failed` (transcription encountered an error). + - `error`: Error description, or `null` when the transcription succeeded. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + callback_url : str + URL that receives the POST callback when transcription completes. + + insights : typing.Optional[bool] + When `true`, generates conversation insights alongside the transcript. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateSpeechAnalyticsResponse + Returns the transcription `request_id` for the uploaded file. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.speech_analytics.create( + callback_url="callback_url", + ) + """ + _response = self._raw_client.create( + file=file, callback_url=callback_url, insights=insights, request_options=request_options + ) + return _response.data + + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetSpeechAnalyticsResponse: + """ + Returns the transcription for the request identified by `request_id`, including transcript, speaker turns, and insights when available. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetSpeechAnalyticsResponse + Returns the completed transcription. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.speech_analytics.get( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", + ) + """ + _response = self._raw_client.get(request_id, request_options=request_options) + return _response.data + + def retranscribe( + self, + request_id: str, + *, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Re-runs transcription on the file identified by `request_id`, replacing the existing transcript. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + callback_url : str + Callback URL for transcription status updates. + + insights : typing.Optional[bool] + Indicates whether to enable insights generation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Retranscription is queued. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.speech_analytics.retranscribe( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", + callback_url="https://you-site.com/webhook", + ) + """ + _response = self._raw_client.retranscribe( + request_id, callback_url=callback_url, insights=insights, request_options=request_options + ) + return _response.data + + @property + def file(self): + if self._file is None: + from .file.client import FileClient # noqa: E402 + + self._file = FileClient(client_wrapper=self._client_wrapper) + return self._file + + +class AsyncSpeechAnalyticsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSpeechAnalyticsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._file: typing.Optional[AsyncFileClient] = None + + @property + def with_raw_response(self) -> AsyncRawSpeechAnalyticsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSpeechAnalyticsClient + """ + return self._raw_client + + async def create( + self, + *, + file: core.File, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateSpeechAnalyticsResponse: + """ + Uploads an audio file for transcription. Transcription is asynchronous; Wavix sends a POST callback to `callback_url` when it completes, including the `request_id` returned by this request. + + Callback body: + ```json + { + "request_id": "e865ea07-25af-4fdd-876e-04b0d41d5ebd", + "status": "completed", + "error": null + } + ``` + + - `request_id`: ID of the transcription request. + - `status`: One of `completed` (transcription succeeded) or `failed` (transcription encountered an error). + - `error`: Error description, or `null` when the transcription succeeded. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + callback_url : str + URL that receives the POST callback when transcription completes. + + insights : typing.Optional[bool] + When `true`, generates conversation insights alongside the transcript. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateSpeechAnalyticsResponse + Returns the transcription `request_id` for the uploaded file. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.speech_analytics.create( + callback_url="callback_url", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + file=file, callback_url=callback_url, insights=insights, request_options=request_options + ) + return _response.data + + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetSpeechAnalyticsResponse: + """ + Returns the transcription for the request identified by `request_id`, including transcript, speaker turns, and insights when available. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetSpeechAnalyticsResponse + Returns the completed transcription. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.speech_analytics.get( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(request_id, request_options=request_options) + return _response.data + + async def retranscribe( + self, + request_id: str, + *, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Re-runs transcription on the file identified by `request_id`, replacing the existing transcript. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + callback_url : str + Callback URL for transcription status updates. + + insights : typing.Optional[bool] + Indicates whether to enable insights generation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. Retranscription is queued. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.speech_analytics.retranscribe( + request_id="e865ea07-25af-4fdd-876e-04b0d41d5ebd", + callback_url="https://you-site.com/webhook", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.retranscribe( + request_id, callback_url=callback_url, insights=insights, request_options=request_options + ) + return _response.data + + @property + def file(self): + if self._file is None: + from .file.client import AsyncFileClient # noqa: E402 + + self._file = AsyncFileClient(client_wrapper=self._client_wrapper) + return self._file diff --git a/src/wavix/speech_analytics/file/__init__.py b/src/wavix/speech_analytics/file/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/speech_analytics/file/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/speech_analytics/file/client.py b/src/wavix/speech_analytics/file/client.py new file mode 100644 index 0000000..182988e --- /dev/null +++ b/src/wavix/speech_analytics/file/client.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawFileClient, RawFileClient + + +class FileClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawFileClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawFileClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawFileClient + """ + return self._raw_client + + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Iterator[bytes]: + """ + Returns the original audio file submitted for the transcription identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[bytes] + Returns the original audio file. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.speech_analytics.file.get( + request_id="request_id", + ) + """ + with self._raw_client.get(request_id, request_options=request_options) as r: + yield from r.data + + +class AsyncFileClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawFileClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawFileClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawFileClient + """ + return self._raw_client + + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[bytes]: + """ + Returns the original audio file submitted for the transcription identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[bytes] + Returns the original audio file. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.speech_analytics.file.get( + request_id="request_id", + ) + + + asyncio.run(main()) + """ + async with self._raw_client.get(request_id, request_options=request_options) as r: + async for _chunk in r.data: + yield _chunk diff --git a/src/wavix/speech_analytics/file/raw_client.py b/src/wavix/speech_analytics/file/raw_client.py new file mode 100644 index 0000000..7bb4a50 --- /dev/null +++ b/src/wavix/speech_analytics/file/raw_client.py @@ -0,0 +1,148 @@ +# This file was auto-generated by Fern from our API Definition. + +import contextlib +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.not_found_error import NotFoundError +from pydantic import ValidationError + + +class RawFileClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + @contextlib.contextmanager + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + """ + Returns the original audio file submitted for the transcription identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + Returns the original audio file. + """ + with self._client_wrapper.httpx_client.stream( + f"v1/speech-analytics/{encode_path_param(request_id)}/file", + method="GET", + request_options=request_options, + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + +class AsyncRawFileClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + @contextlib.asynccontextmanager + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + """ + Returns the original audio file submitted for the transcription identified by `request_id`. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + Returns the original audio file. + """ + async with self._client_wrapper.httpx_client.stream( + f"v1/speech-analytics/{encode_path_param(request_id)}/file", + method="GET", + request_options=request_options, + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() diff --git a/src/wavix/speech_analytics/raw_client.py b/src/wavix/speech_analytics/raw_client.py new file mode 100644 index 0000000..82147b4 --- /dev/null +++ b/src/wavix/speech_analytics/raw_client.py @@ -0,0 +1,527 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from .. import core +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.not_found_error import NotFoundError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.success_response import SuccessResponse +from .types.create_speech_analytics_response import CreateSpeechAnalyticsResponse +from .types.get_speech_analytics_response import GetSpeechAnalyticsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSpeechAnalyticsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + file: core.File, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateSpeechAnalyticsResponse]: + """ + Uploads an audio file for transcription. Transcription is asynchronous; Wavix sends a POST callback to `callback_url` when it completes, including the `request_id` returned by this request. + + Callback body: + ```json + { + "request_id": "e865ea07-25af-4fdd-876e-04b0d41d5ebd", + "status": "completed", + "error": null + } + ``` + + - `request_id`: ID of the transcription request. + - `status`: One of `completed` (transcription succeeded) or `failed` (transcription encountered an error). + - `error`: Error description, or `null` when the transcription succeeded. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + callback_url : str + URL that receives the POST callback when transcription completes. + + insights : typing.Optional[bool] + When `true`, generates conversation insights alongside the transcript. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateSpeechAnalyticsResponse] + Returns the transcription `request_id` for the uploaded file. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/speech-analytics", + method="POST", + data={ + "callback_url": callback_url, + "insights": insights, + }, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateSpeechAnalyticsResponse, + parse_obj_as( + type_=CreateSpeechAnalyticsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[GetSpeechAnalyticsResponse]: + """ + Returns the transcription for the request identified by `request_id`, including transcript, speaker turns, and insights when available. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetSpeechAnalyticsResponse] + Returns the completed transcription. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/speech-analytics/{encode_path_param(request_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetSpeechAnalyticsResponse, + parse_obj_as( + type_=GetSpeechAnalyticsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def retranscribe( + self, + request_id: str, + *, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SuccessResponse]: + """ + Re-runs transcription on the file identified by `request_id`, replacing the existing transcript. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + callback_url : str + Callback URL for transcription status updates. + + insights : typing.Optional[bool] + Indicates whether to enable insights generation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. Retranscription is queued. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/speech-analytics/{encode_path_param(request_id)}", + method="PUT", + json={ + "callback_url": callback_url, + "insights": insights, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSpeechAnalyticsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + file: core.File, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateSpeechAnalyticsResponse]: + """ + Uploads an audio file for transcription. Transcription is asynchronous; Wavix sends a POST callback to `callback_url` when it completes, including the `request_id` returned by this request. + + Callback body: + ```json + { + "request_id": "e865ea07-25af-4fdd-876e-04b0d41d5ebd", + "status": "completed", + "error": null + } + ``` + + - `request_id`: ID of the transcription request. + - `status`: One of `completed` (transcription succeeded) or `failed` (transcription encountered an error). + - `error`: Error description, or `null` when the transcription succeeded. + + Parameters + ---------- + file : core.File + See core.File for more documentation + + callback_url : str + URL that receives the POST callback when transcription completes. + + insights : typing.Optional[bool] + When `true`, generates conversation insights alongside the transcript. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateSpeechAnalyticsResponse] + Returns the transcription `request_id` for the uploaded file. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/speech-analytics", + method="POST", + data={ + "callback_url": callback_url, + "insights": insights, + }, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateSpeechAnalyticsResponse, + parse_obj_as( + type_=CreateSpeechAnalyticsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, request_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetSpeechAnalyticsResponse]: + """ + Returns the transcription for the request identified by `request_id`, including transcript, speaker turns, and insights when available. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetSpeechAnalyticsResponse] + Returns the completed transcription. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/speech-analytics/{encode_path_param(request_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetSpeechAnalyticsResponse, + parse_obj_as( + type_=GetSpeechAnalyticsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def retranscribe( + self, + request_id: str, + *, + callback_url: str, + insights: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Re-runs transcription on the file identified by `request_id`, replacing the existing transcript. + + Parameters + ---------- + request_id : str + The `request_id` of the transcription, returned when the file was uploaded. + + callback_url : str + Callback URL for transcription status updates. + + insights : typing.Optional[bool] + Indicates whether to enable insights generation. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. Retranscription is queued. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/speech-analytics/{encode_path_param(request_id)}", + method="PUT", + json={ + "callback_url": callback_url, + "insights": insights, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/speech_analytics/types/__init__.py b/src/wavix/speech_analytics/types/__init__.py new file mode 100644 index 0000000..5a1f760 --- /dev/null +++ b/src/wavix/speech_analytics/types/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_speech_analytics_response import CreateSpeechAnalyticsResponse + from .get_speech_analytics_response import GetSpeechAnalyticsResponse + from .get_speech_analytics_response_language import GetSpeechAnalyticsResponseLanguage + from .get_speech_analytics_response_status import GetSpeechAnalyticsResponseStatus +_dynamic_imports: typing.Dict[str, str] = { + "CreateSpeechAnalyticsResponse": ".create_speech_analytics_response", + "GetSpeechAnalyticsResponse": ".get_speech_analytics_response", + "GetSpeechAnalyticsResponseLanguage": ".get_speech_analytics_response_language", + "GetSpeechAnalyticsResponseStatus": ".get_speech_analytics_response_status", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateSpeechAnalyticsResponse", + "GetSpeechAnalyticsResponse", + "GetSpeechAnalyticsResponseLanguage", + "GetSpeechAnalyticsResponseStatus", +] diff --git a/src/wavix/speech_analytics/types/create_speech_analytics_response.py b/src/wavix/speech_analytics/types/create_speech_analytics_response.py new file mode 100644 index 0000000..e36a017 --- /dev/null +++ b/src/wavix/speech_analytics/types/create_speech_analytics_response.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateSpeechAnalyticsResponse(UniversalBaseModel): + file: str = pydantic.Field() + """ + Uploaded file name. + """ + + request_id: str = pydantic.Field() + """ + Transcription request ID. + """ + + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/speech_analytics/types/get_speech_analytics_response.py b/src/wavix/speech_analytics/types/get_speech_analytics_response.py new file mode 100644 index 0000000..4565da2 --- /dev/null +++ b/src/wavix/speech_analytics/types/get_speech_analytics_response.py @@ -0,0 +1,77 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...types.file_transcript_response import FileTranscriptResponse +from ...types.file_transcript_turn import FileTranscriptTurn +from .get_speech_analytics_response_language import GetSpeechAnalyticsResponseLanguage +from .get_speech_analytics_response_status import GetSpeechAnalyticsResponseStatus + + +class GetSpeechAnalyticsResponse(UniversalBaseModel): + transcript: typing.Optional[FileTranscriptResponse] = pydantic.Field(default=None) + """ + Complete transcription text attributed to each channel. + """ + + turns: typing.Optional[typing.List[FileTranscriptTurn]] = pydantic.Field(default=None) + """ + List of transcription turns, including speaker attribution, timestamps, and sentiment. + """ + + request_id: str = pydantic.Field() + """ + Transcription request ID. + """ + + language: typing.Optional[GetSpeechAnalyticsResponseLanguage] = pydantic.Field(default=None) + """ + Transcription language. + """ + + duration: typing.Optional[int] = pydantic.Field(default=None) + """ + File duration in seconds. + """ + + charge: str = pydantic.Field() + """ + Total transcription charge in USD. + """ + + status: GetSpeechAnalyticsResponseStatus = pydantic.Field() + """ + Transcription status. Possible values are `completed`, `failed`. + """ + + transcription_date: dt.datetime = pydantic.Field() + """ + Date and time of the transcription in ISO 8601 format. + """ + + transcription_score: typing.Optional[str] = pydantic.Field(default=None) + """ + Conversation sentiment score. Scores from 1.0 to 3.0 are negative; scores from 4.0 to 5.0 are positive. + """ + + transcription_summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcription summary. + """ + + original_file: str = pydantic.Field() + """ + Uploaded file URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/speech_analytics/types/get_speech_analytics_response_language.py b/src/wavix/speech_analytics/types/get_speech_analytics_response_language.py new file mode 100644 index 0000000..41bbedf --- /dev/null +++ b/src/wavix/speech_analytics/types/get_speech_analytics_response_language.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetSpeechAnalyticsResponseLanguage = typing.Union[typing.Literal["en", "de", "es", "fr", "it"], typing.Any] diff --git a/src/wavix/speech_analytics/types/get_speech_analytics_response_status.py b/src/wavix/speech_analytics/types/get_speech_analytics_response_status.py new file mode 100644 index 0000000..49a3509 --- /dev/null +++ b/src/wavix/speech_analytics/types/get_speech_analytics_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetSpeechAnalyticsResponseStatus = typing.Union[typing.Literal["completed", "failed"], typing.Any] diff --git a/src/wavix/sub_accounts/__init__.py b/src/wavix/sub_accounts/__init__.py new file mode 100644 index 0000000..7e52539 --- /dev/null +++ b/src/wavix/sub_accounts/__init__.py @@ -0,0 +1,52 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + ListSubAccountsRequestStatus, + SubAccountsCreateRequestDefaultDestinations, + SubAccountsUpdateRequestDefaultDestinations, + SubAccountsUpdateRequestStatus, + ) + from . import transactions +_dynamic_imports: typing.Dict[str, str] = { + "ListSubAccountsRequestStatus": ".types", + "SubAccountsCreateRequestDefaultDestinations": ".types", + "SubAccountsUpdateRequestDefaultDestinations": ".types", + "SubAccountsUpdateRequestStatus": ".types", + "transactions": ".transactions", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ListSubAccountsRequestStatus", + "SubAccountsCreateRequestDefaultDestinations", + "SubAccountsUpdateRequestDefaultDestinations", + "SubAccountsUpdateRequestStatus", + "transactions", +] diff --git a/src/wavix/sub_accounts/client.py b/src/wavix/sub_accounts/client.py new file mode 100644 index 0000000..3fdbdc5 --- /dev/null +++ b/src/wavix/sub_accounts/client.py @@ -0,0 +1,442 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.sub_accounts_list_response import SubAccountsListResponse +from ..types.sub_organization_response import SubOrganizationResponse +from .raw_client import AsyncRawSubAccountsClient, RawSubAccountsClient +from .types.list_sub_accounts_request_status import ListSubAccountsRequestStatus +from .types.sub_accounts_create_request_default_destinations import SubAccountsCreateRequestDefaultDestinations +from .types.sub_accounts_update_request_default_destinations import SubAccountsUpdateRequestDefaultDestinations +from .types.sub_accounts_update_request_status import SubAccountsUpdateRequestStatus + +if typing.TYPE_CHECKING: + from .transactions.client import AsyncTransactionsClient, TransactionsClient +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SubAccountsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSubAccountsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transactions: typing.Optional[TransactionsClient] = None + + @property + def with_raw_response(self) -> RawSubAccountsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSubAccountsClient + """ + return self._raw_client + + def list( + self, + *, + status: typing.Optional[ListSubAccountsRequestStatus] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubAccountsListResponse: + """ + Returns a paginated list of sub-accounts under the authenticated master account. + + Parameters + ---------- + status : typing.Optional[ListSubAccountsRequestStatus] + Filters sub-accounts by status. One of `enabled` (the sub-account is active) or `disabled` (the sub-account is suspended). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubAccountsListResponse + Returns a paginated list of sub-accounts. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sub_accounts.list() + """ + _response = self._raw_client.list(status=status, request_options=request_options) + return _response.data + + def create( + self, + *, + name: str, + default_destinations: typing.Optional[SubAccountsCreateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubOrganizationResponse: + """ + Creates a sub-account under the authenticated master account. Returns the sub-account with its generated `api_key`. + + Parameters + ---------- + name : str + Sub-account name. + + default_destinations : typing.Optional[SubAccountsCreateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the created sub-account. + + Examples + -------- + from wavix import Wavix + from wavix.sub_accounts import SubAccountsCreateRequestDefaultDestinations + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sub_accounts.create( + name="Company", + default_destinations=SubAccountsCreateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), + ) + """ + _response = self._raw_client.create( + name=name, default_destinations=default_destinations, request_options=request_options + ) + return _response.data + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SubOrganizationResponse: + """ + Returns the sub-account identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the sub-account. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sub_accounts.get( + id=123, + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def update( + self, + id: int, + *, + name: str, + status: typing.Optional[SubAccountsUpdateRequestStatus] = OMIT, + default_destinations: typing.Optional[SubAccountsUpdateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubOrganizationResponse: + """ + Replaces the configuration of the sub-account identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + name : str + Sub-account name. + + status : typing.Optional[SubAccountsUpdateRequestStatus] + Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + + default_destinations : typing.Optional[SubAccountsUpdateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the updated sub-account. + + Examples + -------- + from wavix import Wavix + from wavix.sub_accounts import SubAccountsUpdateRequestDefaultDestinations + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sub_accounts.update( + id=123, + name="Updated Company Name", + status="enabled", + default_destinations=SubAccountsUpdateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), + ) + """ + _response = self._raw_client.update( + id, name=name, status=status, default_destinations=default_destinations, request_options=request_options + ) + return _response.data + + @property + def transactions(self): + if self._transactions is None: + from .transactions.client import TransactionsClient # noqa: E402 + + self._transactions = TransactionsClient(client_wrapper=self._client_wrapper) + return self._transactions + + +class AsyncSubAccountsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSubAccountsClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._transactions: typing.Optional[AsyncTransactionsClient] = None + + @property + def with_raw_response(self) -> AsyncRawSubAccountsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSubAccountsClient + """ + return self._raw_client + + async def list( + self, + *, + status: typing.Optional[ListSubAccountsRequestStatus] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubAccountsListResponse: + """ + Returns a paginated list of sub-accounts under the authenticated master account. + + Parameters + ---------- + status : typing.Optional[ListSubAccountsRequestStatus] + Filters sub-accounts by status. One of `enabled` (the sub-account is active) or `disabled` (the sub-account is suspended). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubAccountsListResponse + Returns a paginated list of sub-accounts. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sub_accounts.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(status=status, request_options=request_options) + return _response.data + + async def create( + self, + *, + name: str, + default_destinations: typing.Optional[SubAccountsCreateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubOrganizationResponse: + """ + Creates a sub-account under the authenticated master account. Returns the sub-account with its generated `api_key`. + + Parameters + ---------- + name : str + Sub-account name. + + default_destinations : typing.Optional[SubAccountsCreateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the created sub-account. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + from wavix.sub_accounts import SubAccountsCreateRequestDefaultDestinations + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sub_accounts.create( + name="Company", + default_destinations=SubAccountsCreateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, default_destinations=default_destinations, request_options=request_options + ) + return _response.data + + async def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> SubOrganizationResponse: + """ + Returns the sub-account identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the sub-account. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sub_accounts.get( + id=123, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def update( + self, + id: int, + *, + name: str, + status: typing.Optional[SubAccountsUpdateRequestStatus] = OMIT, + default_destinations: typing.Optional[SubAccountsUpdateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubOrganizationResponse: + """ + Replaces the configuration of the sub-account identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + name : str + Sub-account name. + + status : typing.Optional[SubAccountsUpdateRequestStatus] + Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + + default_destinations : typing.Optional[SubAccountsUpdateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubOrganizationResponse + Returns the updated sub-account. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + from wavix.sub_accounts import SubAccountsUpdateRequestDefaultDestinations + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sub_accounts.update( + id=123, + name="Updated Company Name", + status="enabled", + default_destinations=SubAccountsUpdateRequestDefaultDestinations( + sms_endpoint="https://examples.com/sms", + dlr_endpoint="https://examples.com/dlr", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + id, name=name, status=status, default_destinations=default_destinations, request_options=request_options + ) + return _response.data + + @property + def transactions(self): + if self._transactions is None: + from .transactions.client import AsyncTransactionsClient # noqa: E402 + + self._transactions = AsyncTransactionsClient(client_wrapper=self._client_wrapper) + return self._transactions diff --git a/src/wavix/sub_accounts/raw_client.py b/src/wavix/sub_accounts/raw_client.py new file mode 100644 index 0000000..e7389dc --- /dev/null +++ b/src/wavix/sub_accounts/raw_client.py @@ -0,0 +1,775 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unauthorized_error import UnauthorizedError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.sub_accounts_list_response import SubAccountsListResponse +from ..types.sub_organization_response import SubOrganizationResponse +from ..types.unauthorized_error_response import UnauthorizedErrorResponse +from .types.list_sub_accounts_request_status import ListSubAccountsRequestStatus +from .types.sub_accounts_create_request_default_destinations import SubAccountsCreateRequestDefaultDestinations +from .types.sub_accounts_update_request_default_destinations import SubAccountsUpdateRequestDefaultDestinations +from .types.sub_accounts_update_request_status import SubAccountsUpdateRequestStatus +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSubAccountsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + status: typing.Optional[ListSubAccountsRequestStatus] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SubAccountsListResponse]: + """ + Returns a paginated list of sub-accounts under the authenticated master account. + + Parameters + ---------- + status : typing.Optional[ListSubAccountsRequestStatus] + Filters sub-accounts by status. One of `enabled` (the sub-account is active) or `disabled` (the sub-account is suspended). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SubAccountsListResponse] + Returns a paginated list of sub-accounts. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/sub-organizations", + method="GET", + params={ + "status": status, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubAccountsListResponse, + parse_obj_as( + type_=SubAccountsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + name: str, + default_destinations: typing.Optional[SubAccountsCreateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SubOrganizationResponse]: + """ + Creates a sub-account under the authenticated master account. Returns the sub-account with its generated `api_key`. + + Parameters + ---------- + name : str + Sub-account name. + + default_destinations : typing.Optional[SubAccountsCreateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SubOrganizationResponse] + Returns the created sub-account. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/sub-organizations", + method="POST", + json={ + "name": name, + "default_destinations": convert_and_respect_annotation_metadata( + object_=default_destinations, + annotation=SubAccountsCreateRequestDefaultDestinations, + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SubOrganizationResponse]: + """ + Returns the sub-account identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SubOrganizationResponse] + Returns the sub-account. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + id: int, + *, + name: str, + status: typing.Optional[SubAccountsUpdateRequestStatus] = OMIT, + default_destinations: typing.Optional[SubAccountsUpdateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SubOrganizationResponse]: + """ + Replaces the configuration of the sub-account identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + name : str + Sub-account name. + + status : typing.Optional[SubAccountsUpdateRequestStatus] + Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + + default_destinations : typing.Optional[SubAccountsUpdateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SubOrganizationResponse] + Returns the updated sub-account. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}", + method="PUT", + json={ + "name": name, + "status": status, + "default_destinations": convert_and_respect_annotation_metadata( + object_=default_destinations, + annotation=SubAccountsUpdateRequestDefaultDestinations, + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSubAccountsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + status: typing.Optional[ListSubAccountsRequestStatus] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SubAccountsListResponse]: + """ + Returns a paginated list of sub-accounts under the authenticated master account. + + Parameters + ---------- + status : typing.Optional[ListSubAccountsRequestStatus] + Filters sub-accounts by status. One of `enabled` (the sub-account is active) or `disabled` (the sub-account is suspended). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SubAccountsListResponse] + Returns a paginated list of sub-accounts. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/sub-organizations", + method="GET", + params={ + "status": status, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubAccountsListResponse, + parse_obj_as( + type_=SubAccountsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + name: str, + default_destinations: typing.Optional[SubAccountsCreateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SubOrganizationResponse]: + """ + Creates a sub-account under the authenticated master account. Returns the sub-account with its generated `api_key`. + + Parameters + ---------- + name : str + Sub-account name. + + default_destinations : typing.Optional[SubAccountsCreateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SubOrganizationResponse] + Returns the created sub-account. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/sub-organizations", + method="POST", + json={ + "name": name, + "default_destinations": convert_and_respect_annotation_metadata( + object_=default_destinations, + annotation=SubAccountsCreateRequestDefaultDestinations, + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SubOrganizationResponse]: + """ + Returns the sub-account identified by `id`. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SubOrganizationResponse] + Returns the sub-account. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + id: int, + *, + name: str, + status: typing.Optional[SubAccountsUpdateRequestStatus] = OMIT, + default_destinations: typing.Optional[SubAccountsUpdateRequestDefaultDestinations] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SubOrganizationResponse]: + """ + Replaces the configuration of the sub-account identified by `id`. Omitted fields revert to their defaults. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + name : str + Sub-account name. + + status : typing.Optional[SubAccountsUpdateRequestStatus] + Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + + default_destinations : typing.Optional[SubAccountsUpdateRequestDefaultDestinations] + Default webhook URLs for inbound messages and delivery reports. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SubOrganizationResponse] + Returns the updated sub-account. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}", + method="PUT", + json={ + "name": name, + "status": status, + "default_destinations": convert_and_respect_annotation_metadata( + object_=default_destinations, + annotation=SubAccountsUpdateRequestDefaultDestinations, + direction="write", + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubOrganizationResponse, + parse_obj_as( + type_=SubOrganizationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sub_accounts/transactions/__init__.py b/src/wavix/sub_accounts/transactions/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/sub_accounts/transactions/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/sub_accounts/transactions/client.py b/src/wavix/sub_accounts/transactions/client.py new file mode 100644 index 0000000..b10e565 --- /dev/null +++ b/src/wavix/sub_accounts/transactions/client.py @@ -0,0 +1,198 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.sub_accounts_transactions_list_response import SubAccountsTransactionsListResponse +from .raw_client import AsyncRawTransactionsClient, RawTransactionsClient + + +class TransactionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTransactionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTransactionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTransactionsClient + """ + return self._raw_client + + def list( + self, + id: int, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubAccountsTransactionsListResponse: + """ + Returns a paginated list of billing transactions for the sub-account identified by `id`, within the requested date range. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Filters transactions by type. Accepts a single transaction type code or an array of codes. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubAccountsTransactionsListResponse + Returns a paginated list of sub-account transactions. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.sub_accounts.transactions.list( + id=123, + from_date=datetime.date.fromisoformat( + "2023-01-01", + ), + to_date=datetime.date.fromisoformat( + "2023-12-31", + ), + type=1, + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + id, + from_date=from_date, + to_date=to_date, + type=type, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + +class AsyncTransactionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTransactionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTransactionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTransactionsClient + """ + return self._raw_client + + async def list( + self, + id: int, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> SubAccountsTransactionsListResponse: + """ + Returns a paginated list of billing transactions for the sub-account identified by `id`, within the requested date range. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Filters transactions by type. Accepts a single transaction type code or an array of codes. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SubAccountsTransactionsListResponse + Returns a paginated list of sub-account transactions. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.sub_accounts.transactions.list( + id=123, + from_date=datetime.date.fromisoformat( + "2023-01-01", + ), + to_date=datetime.date.fromisoformat( + "2023-12-31", + ), + type=1, + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + id, + from_date=from_date, + to_date=to_date, + type=type, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/sub_accounts/transactions/raw_client.py b/src/wavix/sub_accounts/transactions/raw_client.py new file mode 100644 index 0000000..f266c65 --- /dev/null +++ b/src/wavix/sub_accounts/transactions/raw_client.py @@ -0,0 +1,265 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.service_unavailable_error import ServiceUnavailableError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.sub_accounts_transactions_list_response import SubAccountsTransactionsListResponse +from ...types.unauthorized_error_response import UnauthorizedErrorResponse +from ...types.validation_error_response import ValidationErrorResponse +from pydantic import ValidationError + + +class RawTransactionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + id: int, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SubAccountsTransactionsListResponse]: + """ + Returns a paginated list of billing transactions for the sub-account identified by `id`, within the requested date range. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Filters transactions by type. Accepts a single transaction type code or an array of codes. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SubAccountsTransactionsListResponse] + Returns a paginated list of sub-account transactions. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}/billing/transactions", + method="GET", + params={ + "from_date": str(from_date), + "to_date": str(to_date), + "type": type, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubAccountsTransactionsListResponse, + parse_obj_as( + type_=SubAccountsTransactionsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawTransactionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + id: int, + *, + from_date: dt.date, + to_date: dt.date, + type: typing.Optional[typing.Union[int, typing.Sequence[int]]] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SubAccountsTransactionsListResponse]: + """ + Returns a paginated list of billing transactions for the sub-account identified by `id`, within the requested date range. + + Parameters + ---------- + id : int + The unique ID of the sub-account. + + from_date : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to_date : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + type : typing.Optional[typing.Union[int, typing.Sequence[int]]] + Filters transactions by type. Accepts a single transaction type code or an array of codes. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SubAccountsTransactionsListResponse] + Returns a paginated list of sub-account transactions. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/sub-organizations/{encode_path_param(id)}/billing/transactions", + method="GET", + params={ + "from_date": str(from_date), + "to_date": str(to_date), + "type": type, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SubAccountsTransactionsListResponse, + parse_obj_as( + type_=SubAccountsTransactionsListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 503: + raise ServiceUnavailableError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/sub_accounts/types/__init__.py b/src/wavix/sub_accounts/types/__init__.py new file mode 100644 index 0000000..7abfd17 --- /dev/null +++ b/src/wavix/sub_accounts/types/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .list_sub_accounts_request_status import ListSubAccountsRequestStatus + from .sub_accounts_create_request_default_destinations import SubAccountsCreateRequestDefaultDestinations + from .sub_accounts_update_request_default_destinations import SubAccountsUpdateRequestDefaultDestinations + from .sub_accounts_update_request_status import SubAccountsUpdateRequestStatus +_dynamic_imports: typing.Dict[str, str] = { + "ListSubAccountsRequestStatus": ".list_sub_accounts_request_status", + "SubAccountsCreateRequestDefaultDestinations": ".sub_accounts_create_request_default_destinations", + "SubAccountsUpdateRequestDefaultDestinations": ".sub_accounts_update_request_default_destinations", + "SubAccountsUpdateRequestStatus": ".sub_accounts_update_request_status", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "ListSubAccountsRequestStatus", + "SubAccountsCreateRequestDefaultDestinations", + "SubAccountsUpdateRequestDefaultDestinations", + "SubAccountsUpdateRequestStatus", +] diff --git a/src/wavix/sub_accounts/types/list_sub_accounts_request_status.py b/src/wavix/sub_accounts/types/list_sub_accounts_request_status.py new file mode 100644 index 0000000..c2eb827 --- /dev/null +++ b/src/wavix/sub_accounts/types/list_sub_accounts_request_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ListSubAccountsRequestStatus = typing.Union[typing.Literal["enabled", "disabled"], typing.Any] diff --git a/src/wavix/sub_accounts/types/sub_accounts_create_request_default_destinations.py b/src/wavix/sub_accounts/types/sub_accounts_create_request_default_destinations.py new file mode 100644 index 0000000..63b7543 --- /dev/null +++ b/src/wavix/sub_accounts/types/sub_accounts_create_request_default_destinations.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SubAccountsCreateRequestDefaultDestinations(UniversalBaseModel): + """ + Default webhook URLs for inbound messages and delivery reports. + """ + + sms_endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Inbound messages webhook URL. + """ + + dlr_endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Delivery report webhook URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sub_accounts/types/sub_accounts_update_request_default_destinations.py b/src/wavix/sub_accounts/types/sub_accounts_update_request_default_destinations.py new file mode 100644 index 0000000..abd43ed --- /dev/null +++ b/src/wavix/sub_accounts/types/sub_accounts_update_request_default_destinations.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SubAccountsUpdateRequestDefaultDestinations(UniversalBaseModel): + """ + Default webhook URLs for inbound messages and delivery reports. + """ + + sms_endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Inbound messages webhook URL. + """ + + dlr_endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Delivery report webhook URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/sub_accounts/types/sub_accounts_update_request_status.py b/src/wavix/sub_accounts/types/sub_accounts_update_request_status.py new file mode 100644 index 0000000..331b8fd --- /dev/null +++ b/src/wavix/sub_accounts/types/sub_accounts_update_request_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubAccountsUpdateRequestStatus = typing.Union[typing.Literal["enabled", "disabled"], typing.Any] diff --git a/src/wavix/ten_dlc/__init__.py b/src/wavix/ten_dlc/__init__.py new file mode 100644 index 0000000..1c6b556 --- /dev/null +++ b/src/wavix/ten_dlc/__init__.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import ( + brand_appeals, + brand_evidence, + brand_vetting_appeals, + brand_vettings, + brands, + campaign_numbers, + campaigns, + subscriptions, + ) + from .brand_appeals import CreateBrandAppealsResponse + from .brand_evidence import DeleteBrandEvidenceResponse, UploadBrandEvidenceResponse + from .brand_vetting_appeals import CreateBrandVettingAppealsResponse + from .brands import ( + CreateBrandsResponse, + CreateBrandsResponseEntityType, + CreateBrandsResponseStatus, + DeleteBrandsResponse, + GetBrandsResponse, + GetBrandsResponseEntityType, + GetBrandsResponseStatus, + ListBrandsResponse, + ListBrandsResponsePagination, + QualifyUsecaseBrandsRequestUseCase, + QualifyUsecaseBrandsResponse, + TenDlcBrandUpdateRequestEntityType, + TenDlcBrandUpdateRequestVertical, + UpdateBrandsResponse, + UpdateBrandsResponseEntityType, + UpdateBrandsResponseStatus, + ) + from .campaign_numbers import ( + LinkCampaignNumbersResponse, + ListCampaignNumbersResponse, + UnlinkCampaignNumbersResponse, + ) + from .campaigns import ( + CreateCampaignsResponse, + DeleteCampaignsResponse, + GetCampaignsResponse, + ListByBrandCampaignsResponse, + ListByBrandCampaignsResponsePagination, + ListCampaignsResponse, + ListCampaignsResponsePagination, + TenDlcCampaignUpdateRequestUsecase, + UpdateCampaignsResponse, + ) + from .subscriptions import CreateSubscriptionsResponse, DeleteSubscriptionsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateBrandAppealsResponse": ".brand_appeals", + "CreateBrandVettingAppealsResponse": ".brand_vetting_appeals", + "CreateBrandsResponse": ".brands", + "CreateBrandsResponseEntityType": ".brands", + "CreateBrandsResponseStatus": ".brands", + "CreateCampaignsResponse": ".campaigns", + "CreateSubscriptionsResponse": ".subscriptions", + "DeleteBrandEvidenceResponse": ".brand_evidence", + "DeleteBrandsResponse": ".brands", + "DeleteCampaignsResponse": ".campaigns", + "DeleteSubscriptionsResponse": ".subscriptions", + "GetBrandsResponse": ".brands", + "GetBrandsResponseEntityType": ".brands", + "GetBrandsResponseStatus": ".brands", + "GetCampaignsResponse": ".campaigns", + "LinkCampaignNumbersResponse": ".campaign_numbers", + "ListBrandsResponse": ".brands", + "ListBrandsResponsePagination": ".brands", + "ListByBrandCampaignsResponse": ".campaigns", + "ListByBrandCampaignsResponsePagination": ".campaigns", + "ListCampaignNumbersResponse": ".campaign_numbers", + "ListCampaignsResponse": ".campaigns", + "ListCampaignsResponsePagination": ".campaigns", + "QualifyUsecaseBrandsRequestUseCase": ".brands", + "QualifyUsecaseBrandsResponse": ".brands", + "TenDlcBrandUpdateRequestEntityType": ".brands", + "TenDlcBrandUpdateRequestVertical": ".brands", + "TenDlcCampaignUpdateRequestUsecase": ".campaigns", + "UnlinkCampaignNumbersResponse": ".campaign_numbers", + "UpdateBrandsResponse": ".brands", + "UpdateBrandsResponseEntityType": ".brands", + "UpdateBrandsResponseStatus": ".brands", + "UpdateCampaignsResponse": ".campaigns", + "UploadBrandEvidenceResponse": ".brand_evidence", + "brand_appeals": ".brand_appeals", + "brand_evidence": ".brand_evidence", + "brand_vetting_appeals": ".brand_vetting_appeals", + "brand_vettings": ".brand_vettings", + "brands": ".brands", + "campaign_numbers": ".campaign_numbers", + "campaigns": ".campaigns", + "subscriptions": ".subscriptions", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateBrandAppealsResponse", + "CreateBrandVettingAppealsResponse", + "CreateBrandsResponse", + "CreateBrandsResponseEntityType", + "CreateBrandsResponseStatus", + "CreateCampaignsResponse", + "CreateSubscriptionsResponse", + "DeleteBrandEvidenceResponse", + "DeleteBrandsResponse", + "DeleteCampaignsResponse", + "DeleteSubscriptionsResponse", + "GetBrandsResponse", + "GetBrandsResponseEntityType", + "GetBrandsResponseStatus", + "GetCampaignsResponse", + "LinkCampaignNumbersResponse", + "ListBrandsResponse", + "ListBrandsResponsePagination", + "ListByBrandCampaignsResponse", + "ListByBrandCampaignsResponsePagination", + "ListCampaignNumbersResponse", + "ListCampaignsResponse", + "ListCampaignsResponsePagination", + "QualifyUsecaseBrandsRequestUseCase", + "QualifyUsecaseBrandsResponse", + "TenDlcBrandUpdateRequestEntityType", + "TenDlcBrandUpdateRequestVertical", + "TenDlcCampaignUpdateRequestUsecase", + "UnlinkCampaignNumbersResponse", + "UpdateBrandsResponse", + "UpdateBrandsResponseEntityType", + "UpdateBrandsResponseStatus", + "UpdateCampaignsResponse", + "UploadBrandEvidenceResponse", + "brand_appeals", + "brand_evidence", + "brand_vetting_appeals", + "brand_vettings", + "brands", + "campaign_numbers", + "campaigns", + "subscriptions", +] diff --git a/src/wavix/ten_dlc/brand_appeals/__init__.py b/src/wavix/ten_dlc/brand_appeals/__init__.py new file mode 100644 index 0000000..dd0b21c --- /dev/null +++ b/src/wavix/ten_dlc/brand_appeals/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateBrandAppealsResponse +_dynamic_imports: typing.Dict[str, str] = {"CreateBrandAppealsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateBrandAppealsResponse"] diff --git a/src/wavix/ten_dlc/brand_appeals/client.py b/src/wavix/ten_dlc/brand_appeals/client.py new file mode 100644 index 0000000..fbdb34d --- /dev/null +++ b/src/wavix/ten_dlc/brand_appeals/client.py @@ -0,0 +1,244 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.ten_dlc_brand_appeal import TenDlcBrandAppeal +from .raw_client import AsyncRawBrandAppealsClient, RawBrandAppealsClient +from .types.create_brand_appeals_response import CreateBrandAppealsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BrandAppealsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBrandAppealsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawBrandAppealsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBrandAppealsClient + """ + return self._raw_client + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandAppeal]: + """ + Returns the identity verification appeals submitted for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandAppeal] + Returns the list of identity verification appeals. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_appeals.list( + brand_id="BM20QP9", + ) + """ + _response = self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateBrandAppealsResponse: + """ + Submits an appeal for 10DLC brand identity verification. Provide any additional documentation to support the appeal. Use `appeal_category` to specify the appeal type: + - `VERIFY_TAX_ID` — Use if the brand is UNVERIFIED due to a tax ID mismatch. Applies to private companies, public companies, non-profits, and government entities. + - `VERIFY_NON_PROFIT` — Use if a non-profit brand is UNVERIFIED or VERIFIED but missing tax-exempt status. + - `VERIFY_GOVERNMENT` — Use if a government brand is UNVERIFIED or VERIFIED but missing government entity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandAppealsResponse + Returns the submitted appeal. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_appeals.create( + brand_id="BM20QP9", + appeal_categories=["VERIFY_TAX_ID"], + evidence=["855dff49-c097-4645-3983-08dcb9856232"], + ) + """ + _response = self._raw_client.create( + brand_id, + appeal_categories=appeal_categories, + evidence=evidence, + explanation=explanation, + request_options=request_options, + ) + return _response.data + + +class AsyncBrandAppealsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBrandAppealsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawBrandAppealsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBrandAppealsClient + """ + return self._raw_client + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandAppeal]: + """ + Returns the identity verification appeals submitted for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandAppeal] + Returns the list of identity verification appeals. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_appeals.list( + brand_id="BM20QP9", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + async def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateBrandAppealsResponse: + """ + Submits an appeal for 10DLC brand identity verification. Provide any additional documentation to support the appeal. Use `appeal_category` to specify the appeal type: + - `VERIFY_TAX_ID` — Use if the brand is UNVERIFIED due to a tax ID mismatch. Applies to private companies, public companies, non-profits, and government entities. + - `VERIFY_NON_PROFIT` — Use if a non-profit brand is UNVERIFIED or VERIFIED but missing tax-exempt status. + - `VERIFY_GOVERNMENT` — Use if a government brand is UNVERIFIED or VERIFIED but missing government entity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandAppealsResponse + Returns the submitted appeal. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_appeals.create( + brand_id="BM20QP9", + appeal_categories=["VERIFY_TAX_ID"], + evidence=["855dff49-c097-4645-3983-08dcb9856232"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + brand_id, + appeal_categories=appeal_categories, + evidence=evidence, + explanation=explanation, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/ten_dlc/brand_appeals/raw_client.py b/src/wavix/ten_dlc/brand_appeals/raw_client.py new file mode 100644 index 0000000..d2fc6b9 --- /dev/null +++ b/src/wavix/ten_dlc/brand_appeals/raw_client.py @@ -0,0 +1,390 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.ten_dlc_brand_appeal import TenDlcBrandAppeal +from .types.create_brand_appeals_response import CreateBrandAppealsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBrandAppealsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[TenDlcBrandAppeal]]: + """ + Returns the identity verification appeals submitted for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[TenDlcBrandAppeal]] + Returns the list of identity verification appeals. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/appeals", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandAppeal], + parse_obj_as( + type_=typing.List[TenDlcBrandAppeal], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateBrandAppealsResponse]: + """ + Submits an appeal for 10DLC brand identity verification. Provide any additional documentation to support the appeal. Use `appeal_category` to specify the appeal type: + - `VERIFY_TAX_ID` — Use if the brand is UNVERIFIED due to a tax ID mismatch. Applies to private companies, public companies, non-profits, and government entities. + - `VERIFY_NON_PROFIT` — Use if a non-profit brand is UNVERIFIED or VERIFIED but missing tax-exempt status. + - `VERIFY_GOVERNMENT` — Use if a government brand is UNVERIFIED or VERIFIED but missing government entity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateBrandAppealsResponse] + Returns the submitted appeal. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/appeals", + method="POST", + json={ + "appeal_categories": appeal_categories, + "evidence": evidence, + "explanation": explanation, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandAppealsResponse, + parse_obj_as( + type_=CreateBrandAppealsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawBrandAppealsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[TenDlcBrandAppeal]]: + """ + Returns the identity verification appeals submitted for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[TenDlcBrandAppeal]] + Returns the list of identity verification appeals. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/appeals", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandAppeal], + parse_obj_as( + type_=typing.List[TenDlcBrandAppeal], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateBrandAppealsResponse]: + """ + Submits an appeal for 10DLC brand identity verification. Provide any additional documentation to support the appeal. Use `appeal_category` to specify the appeal type: + - `VERIFY_TAX_ID` — Use if the brand is UNVERIFIED due to a tax ID mismatch. Applies to private companies, public companies, non-profits, and government entities. + - `VERIFY_NON_PROFIT` — Use if a non-profit brand is UNVERIFIED or VERIFIED but missing tax-exempt status. + - `VERIFY_GOVERNMENT` — Use if a government brand is UNVERIFIED or VERIFIED but missing government entity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateBrandAppealsResponse] + Returns the submitted appeal. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/appeals", + method="POST", + json={ + "appeal_categories": appeal_categories, + "evidence": evidence, + "explanation": explanation, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandAppealsResponse, + parse_obj_as( + type_=CreateBrandAppealsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/brand_appeals/types/__init__.py b/src/wavix/ten_dlc/brand_appeals/types/__init__.py new file mode 100644 index 0000000..78dbbfb --- /dev/null +++ b/src/wavix/ten_dlc/brand_appeals/types/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_brand_appeals_response import CreateBrandAppealsResponse +_dynamic_imports: typing.Dict[str, str] = {"CreateBrandAppealsResponse": ".create_brand_appeals_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateBrandAppealsResponse"] diff --git a/src/wavix/ten_dlc/brand_appeals/types/create_brand_appeals_response.py b/src/wavix/ten_dlc/brand_appeals/types/create_brand_appeals_response.py new file mode 100644 index 0000000..2d157d0 --- /dev/null +++ b/src/wavix/ten_dlc/brand_appeals/types/create_brand_appeals_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateBrandAppealsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brand_evidence/__init__.py b/src/wavix/ten_dlc/brand_evidence/__init__.py new file mode 100644 index 0000000..bf7e85c --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/__init__.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import DeleteBrandEvidenceResponse, UploadBrandEvidenceResponse +_dynamic_imports: typing.Dict[str, str] = { + "DeleteBrandEvidenceResponse": ".types", + "UploadBrandEvidenceResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteBrandEvidenceResponse", "UploadBrandEvidenceResponse"] diff --git a/src/wavix/ten_dlc/brand_evidence/client.py b/src/wavix/ten_dlc/brand_evidence/client.py new file mode 100644 index 0000000..c99db97 --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/client.py @@ -0,0 +1,365 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ... import core +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.list_brand_evidence_response import ListBrandEvidenceResponse +from .raw_client import AsyncRawBrandEvidenceClient, RawBrandEvidenceClient +from .types.delete_brand_evidence_response import DeleteBrandEvidenceResponse +from .types.upload_brand_evidence_response import UploadBrandEvidenceResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BrandEvidenceClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBrandEvidenceClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawBrandEvidenceClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBrandEvidenceClient + """ + return self._raw_client + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ListBrandEvidenceResponse: + """ + Returns the evidence files uploaded for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListBrandEvidenceResponse + Returns the list of uploaded evidence files. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_evidence.list( + brand_id="B6AI7PA", + ) + """ + _response = self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + def upload( + self, brand_id: str, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> UploadBrandEvidenceResponse: + """ + Uploads a supporting evidence file for the 10DLC Brand identified by `brand_id`. Supported formats include `.jpg`, `.png`, and `.pdf`. Maximum size is 10 MB. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UploadBrandEvidenceResponse + Returns the uploaded evidence file. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_evidence.upload( + brand_id="B6AI7PA", + ) + """ + _response = self._raw_client.upload(brand_id, file=file, request_options=request_options) + return _response.data + + def get( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Iterator[bytes]: + """ + Returns the Brand evidence file identified by the evidence ID. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[bytes] + Returns the Brand evidence file. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_evidence.get( + brand_id="brand_id", + id="id", + ) + """ + with self._raw_client.get(brand_id, id, request_options=request_options) as r: + yield from r.data + + def delete( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteBrandEvidenceResponse: + """ + Deletes the Brand evidence file identified by the evidence ID. Deletion is permanent. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteBrandEvidenceResponse + Returns a success confirmation. The evidence file is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_evidence.delete( + brand_id="B6AI7PA", + id="191eb205-8357-4d71-b8da-160a25a000d7", + ) + """ + _response = self._raw_client.delete(brand_id, id, request_options=request_options) + return _response.data + + +class AsyncBrandEvidenceClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBrandEvidenceClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawBrandEvidenceClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBrandEvidenceClient + """ + return self._raw_client + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ListBrandEvidenceResponse: + """ + Returns the evidence files uploaded for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListBrandEvidenceResponse + Returns the list of uploaded evidence files. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_evidence.list( + brand_id="B6AI7PA", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + async def upload( + self, brand_id: str, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> UploadBrandEvidenceResponse: + """ + Uploads a supporting evidence file for the 10DLC Brand identified by `brand_id`. Supported formats include `.jpg`, `.png`, and `.pdf`. Maximum size is 10 MB. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UploadBrandEvidenceResponse + Returns the uploaded evidence file. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_evidence.upload( + brand_id="B6AI7PA", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.upload(brand_id, file=file, request_options=request_options) + return _response.data + + async def get( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[bytes]: + """ + Returns the Brand evidence file identified by the evidence ID. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[bytes] + Returns the Brand evidence file. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_evidence.get( + brand_id="brand_id", + id="id", + ) + + + asyncio.run(main()) + """ + async with self._raw_client.get(brand_id, id, request_options=request_options) as r: + async for _chunk in r.data: + yield _chunk + + async def delete( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteBrandEvidenceResponse: + """ + Deletes the Brand evidence file identified by the evidence ID. Deletion is permanent. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteBrandEvidenceResponse + Returns a success confirmation. The evidence file is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_evidence.delete( + brand_id="B6AI7PA", + id="191eb205-8357-4d71-b8da-160a25a000d7", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(brand_id, id, request_options=request_options) + return _response.data diff --git a/src/wavix/ten_dlc/brand_evidence/raw_client.py b/src/wavix/ten_dlc/brand_evidence/raw_client.py new file mode 100644 index 0000000..24b1e9c --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/raw_client.py @@ -0,0 +1,599 @@ +# This file was auto-generated by Fern from our API Definition. + +import contextlib +import typing +from json.decoder import JSONDecodeError + +from ... import core +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.list_brand_evidence_response import ListBrandEvidenceResponse +from .types.delete_brand_evidence_response import DeleteBrandEvidenceResponse +from .types.upload_brand_evidence_response import UploadBrandEvidenceResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBrandEvidenceClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ListBrandEvidenceResponse]: + """ + Returns the evidence files uploaded for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListBrandEvidenceResponse] + Returns the list of uploaded evidence files. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListBrandEvidenceResponse, + parse_obj_as( + type_=ListBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def upload( + self, brand_id: str, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[UploadBrandEvidenceResponse]: + """ + Uploads a supporting evidence file for the 10DLC Brand identified by `brand_id`. Supported formats include `.jpg`, `.png`, and `.pdf`. Maximum size is 10 MB. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UploadBrandEvidenceResponse] + Returns the uploaded evidence file. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UploadBrandEvidenceResponse, + parse_obj_as( + type_=UploadBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + @contextlib.contextmanager + def get( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + """ + Returns the Brand evidence file identified by the evidence ID. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + Returns the Brand evidence file. + """ + with self._client_wrapper.httpx_client.stream( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + def delete( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteBrandEvidenceResponse]: + """ + Deletes the Brand evidence file identified by the evidence ID. Deletion is permanent. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteBrandEvidenceResponse] + Returns a success confirmation. The evidence file is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteBrandEvidenceResponse, + parse_obj_as( + type_=DeleteBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawBrandEvidenceClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ListBrandEvidenceResponse]: + """ + Returns the evidence files uploaded for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListBrandEvidenceResponse] + Returns the list of uploaded evidence files. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListBrandEvidenceResponse, + parse_obj_as( + type_=ListBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def upload( + self, brand_id: str, *, file: core.File, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[UploadBrandEvidenceResponse]: + """ + Uploads a supporting evidence file for the 10DLC Brand identified by `brand_id`. Supported formats include `.jpg`, `.png`, and `.pdf`. Maximum size is 10 MB. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + file : core.File + See core.File for more documentation + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UploadBrandEvidenceResponse] + Returns the uploaded evidence file. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence", + method="POST", + data={}, + files={ + "file": file, + }, + request_options=request_options, + omit=OMIT, + force_multipart=True, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UploadBrandEvidenceResponse, + parse_obj_as( + type_=UploadBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + @contextlib.asynccontextmanager + async def get( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + """ + Returns the Brand evidence file identified by the evidence ID. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. + + Returns + ------- + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + Returns the Brand evidence file. + """ + async with self._client_wrapper.httpx_client.stream( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.json(), + cause=e, + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() + + async def delete( + self, brand_id: str, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteBrandEvidenceResponse]: + """ + Deletes the Brand evidence file identified by the evidence ID. Deletion is permanent. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + id : str + The unique ID of the Brand evidence file. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteBrandEvidenceResponse] + Returns a success confirmation. The evidence file is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/evidence/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteBrandEvidenceResponse, + parse_obj_as( + type_=DeleteBrandEvidenceResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/brand_evidence/types/__init__.py b/src/wavix/ten_dlc/brand_evidence/types/__init__.py new file mode 100644 index 0000000..464bd76 --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .delete_brand_evidence_response import DeleteBrandEvidenceResponse + from .upload_brand_evidence_response import UploadBrandEvidenceResponse +_dynamic_imports: typing.Dict[str, str] = { + "DeleteBrandEvidenceResponse": ".delete_brand_evidence_response", + "UploadBrandEvidenceResponse": ".upload_brand_evidence_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["DeleteBrandEvidenceResponse", "UploadBrandEvidenceResponse"] diff --git a/src/wavix/ten_dlc/brand_evidence/types/delete_brand_evidence_response.py b/src/wavix/ten_dlc/brand_evidence/types/delete_brand_evidence_response.py new file mode 100644 index 0000000..e76071c --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/types/delete_brand_evidence_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteBrandEvidenceResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brand_evidence/types/upload_brand_evidence_response.py b/src/wavix/ten_dlc/brand_evidence/types/upload_brand_evidence_response.py new file mode 100644 index 0000000..dda8332 --- /dev/null +++ b/src/wavix/ten_dlc/brand_evidence/types/upload_brand_evidence_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....core.serialization import FieldMetadata + + +class UploadBrandEvidenceResponse(UniversalBaseModel): + file_name: str = pydantic.Field() + """ + The uploaded file name + """ + + mime_type: str = pydantic.Field() + """ + The uploaded file media type + """ + + url: str = pydantic.Field() + """ + An URL to the uploaded file + """ + + uuid_: typing_extensions.Annotated[ + str, FieldMetadata(alias="uuid"), pydantic.Field(alias="uuid", description="The evidence UUID") + ] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brand_vetting_appeals/__init__.py b/src/wavix/ten_dlc/brand_vetting_appeals/__init__.py new file mode 100644 index 0000000..be9f0c5 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vetting_appeals/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateBrandVettingAppealsResponse +_dynamic_imports: typing.Dict[str, str] = {"CreateBrandVettingAppealsResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateBrandVettingAppealsResponse"] diff --git a/src/wavix/ten_dlc/brand_vetting_appeals/client.py b/src/wavix/ten_dlc/brand_vetting_appeals/client.py new file mode 100644 index 0000000..e723499 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vetting_appeals/client.py @@ -0,0 +1,258 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.ten_dlc_brand_vetting_appeal import TenDlcBrandVettingAppeal +from .raw_client import AsyncRawBrandVettingAppealsClient, RawBrandVettingAppealsClient +from .types.create_brand_vetting_appeals_response import CreateBrandVettingAppealsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BrandVettingAppealsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBrandVettingAppealsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawBrandVettingAppealsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBrandVettingAppealsClient + """ + return self._raw_client + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandVettingAppeal]: + """ + Returns the external vetting appeals for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandVettingAppeal] + Returns the list of external vetting appeals. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_vetting_appeals.list( + brand_id="BMQFB7X", + ) + """ + _response = self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + evp_id: typing.Optional[str] = OMIT, + vetting_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateBrandVettingAppealsResponse: + """ + Submits an appeal for an external vetting of the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT`, `LOW_SCORE`. + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + evp_id : typing.Optional[str] + EVP ID. + + vetting_id : typing.Optional[str] + Vetting ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandVettingAppealsResponse + Returns the submitted appeal. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_vetting_appeals.create( + brand_id="B6AI7PA", + appeal_categories=["VERIFY_TAX_ID"], + evidence=["855dff49-c097-4645-3983-08dcb9856232"], + ) + """ + _response = self._raw_client.create( + brand_id, + appeal_categories=appeal_categories, + evidence=evidence, + explanation=explanation, + evp_id=evp_id, + vetting_id=vetting_id, + request_options=request_options, + ) + return _response.data + + +class AsyncBrandVettingAppealsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBrandVettingAppealsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawBrandVettingAppealsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBrandVettingAppealsClient + """ + return self._raw_client + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandVettingAppeal]: + """ + Returns the external vetting appeals for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandVettingAppeal] + Returns the list of external vetting appeals. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_vetting_appeals.list( + brand_id="BMQFB7X", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + async def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + evp_id: typing.Optional[str] = OMIT, + vetting_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateBrandVettingAppealsResponse: + """ + Submits an appeal for an external vetting of the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT`, `LOW_SCORE`. + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + evp_id : typing.Optional[str] + EVP ID. + + vetting_id : typing.Optional[str] + Vetting ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandVettingAppealsResponse + Returns the submitted appeal. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_vetting_appeals.create( + brand_id="B6AI7PA", + appeal_categories=["VERIFY_TAX_ID"], + evidence=["855dff49-c097-4645-3983-08dcb9856232"], + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + brand_id, + appeal_categories=appeal_categories, + evidence=evidence, + explanation=explanation, + evp_id=evp_id, + vetting_id=vetting_id, + request_options=request_options, + ) + return _response.data diff --git a/src/wavix/ten_dlc/brand_vetting_appeals/raw_client.py b/src/wavix/ten_dlc/brand_vetting_appeals/raw_client.py new file mode 100644 index 0000000..d1fbe48 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vetting_appeals/raw_client.py @@ -0,0 +1,381 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.ten_dlc_brand_vetting_appeal import TenDlcBrandVettingAppeal +from .types.create_brand_vetting_appeals_response import CreateBrandVettingAppealsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBrandVettingAppealsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[TenDlcBrandVettingAppeal]]: + """ + Returns the external vetting appeals for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[TenDlcBrandVettingAppeal]] + Returns the list of external vetting appeals. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings/appeals", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandVettingAppeal], + parse_obj_as( + type_=typing.List[TenDlcBrandVettingAppeal], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + evp_id: typing.Optional[str] = OMIT, + vetting_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateBrandVettingAppealsResponse]: + """ + Submits an appeal for an external vetting of the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT`, `LOW_SCORE`. + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + evp_id : typing.Optional[str] + EVP ID. + + vetting_id : typing.Optional[str] + Vetting ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateBrandVettingAppealsResponse] + Returns the submitted appeal. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings/appeals", + method="POST", + json={ + "appeal_categories": appeal_categories, + "evidence": evidence, + "explanation": explanation, + "evp_id": evp_id, + "vetting_id": vetting_id, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandVettingAppealsResponse, + parse_obj_as( + type_=CreateBrandVettingAppealsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawBrandVettingAppealsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[TenDlcBrandVettingAppeal]]: + """ + Returns the external vetting appeals for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[TenDlcBrandVettingAppeal]] + Returns the list of external vetting appeals. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings/appeals", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandVettingAppeal], + parse_obj_as( + type_=typing.List[TenDlcBrandVettingAppeal], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + brand_id: str, + *, + appeal_categories: typing.Sequence[str], + evidence: typing.Sequence[str], + explanation: typing.Optional[str] = OMIT, + evp_id: typing.Optional[str] = OMIT, + vetting_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateBrandVettingAppealsResponse]: + """ + Submits an appeal for an external vetting of the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + appeal_categories : typing.Sequence[str] + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT`, `LOW_SCORE`. + + evidence : typing.Sequence[str] + List of evidence IDs associated with the appeal. + + explanation : typing.Optional[str] + Appeal comment or justification. + + evp_id : typing.Optional[str] + EVP ID. + + vetting_id : typing.Optional[str] + Vetting ID. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateBrandVettingAppealsResponse] + Returns the submitted appeal. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings/appeals", + method="POST", + json={ + "appeal_categories": appeal_categories, + "evidence": evidence, + "explanation": explanation, + "evp_id": evp_id, + "vetting_id": vetting_id, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandVettingAppealsResponse, + parse_obj_as( + type_=CreateBrandVettingAppealsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/brand_vetting_appeals/types/__init__.py b/src/wavix/ten_dlc/brand_vetting_appeals/types/__init__.py new file mode 100644 index 0000000..cf6edab --- /dev/null +++ b/src/wavix/ten_dlc/brand_vetting_appeals/types/__init__.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_brand_vetting_appeals_response import CreateBrandVettingAppealsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateBrandVettingAppealsResponse": ".create_brand_vetting_appeals_response" +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateBrandVettingAppealsResponse"] diff --git a/src/wavix/ten_dlc/brand_vetting_appeals/types/create_brand_vetting_appeals_response.py b/src/wavix/ten_dlc/brand_vetting_appeals/types/create_brand_vetting_appeals_response.py new file mode 100644 index 0000000..0897e20 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vetting_appeals/types/create_brand_vetting_appeals_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateBrandVettingAppealsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brand_vettings/__init__.py b/src/wavix/ten_dlc/brand_vettings/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vettings/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/ten_dlc/brand_vettings/client.py b/src/wavix/ten_dlc/brand_vettings/client.py new file mode 100644 index 0000000..e1d81c7 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vettings/client.py @@ -0,0 +1,325 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.ten_dlc_brand_vetting import TenDlcBrandVetting +from .raw_client import AsyncRawBrandVettingsClient, RawBrandVettingsClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BrandVettingsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBrandVettingsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawBrandVettingsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBrandVettingsClient + """ + return self._raw_client + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandVetting]: + """ + Returns the external vettings for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandVetting] + Returns the list of external vettings. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_vettings.list( + brand_id="B6AI7PA", + ) + """ + _response = self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + def create( + self, brand_id: str, *, evp_id: str, vetting_class: str, request_options: typing.Optional[RequestOptions] = None + ) -> TenDlcBrandVetting: + """ + Requests external vetting for a 10DLC Brand. Supported providers: `AEGIS`, `CV`, `WMC`. Supported classes: `STANDARD`, `ENHANCED`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider to perform the vetting. + + vetting_class : str + Class of vetting to request, such as `STANDARD` or `ENHANCED`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TenDlcBrandVetting + Returns the requested external vetting. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_vettings.create( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_class="STANDARD", + ) + """ + _response = self._raw_client.create( + brand_id, evp_id=evp_id, vetting_class=vetting_class, request_options=request_options + ) + return _response.data + + def import_( + self, + brand_id: str, + *, + evp_id: str, + vetting_id: str, + vetting_token: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> TenDlcBrandVetting: + """ + Imports an existing external vetting record into the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider that issued the vetting. + + vetting_id : str + Unique identifier of the vetting request to import. + + vetting_token : str + Token issued by the vetting provider that uniquely identifies the vetting result to import. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TenDlcBrandVetting + Returns the imported external vetting. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brand_vettings.import_( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_id="13d8e00c-3cb4-4dc0-9e26-d5057fa938d9", + vetting_token="3oDcE1vq8OR43claMa6Thu/7V4vzZywAfKRgiJnXDjlw+08wpWbGqOssAXKgeZibHCLaGgXvU/yPb7kISeeb5qGdisGRLdhPnSNpvRR82RnCWYNpTp92orlJWjTJU8ZGmNxL5MwK0tt/9SxCha36iTtPV2+4vND8xCPe5suItuQTonG4A3Yi6F1LMqihgwdesRjxJnKqcE7Thcv9ug1NyNPYEZQvPugFj2F2DdU6jFZcOWgXsnE7ucZ+xNaNX9LkF9if3v0hrcviG9L8bUUrpPBGr02txP0i+cPBTLbj4Rq1Ox83R+WUx1gnoXHCIU1ByDGWvQq2Ef4qxGVOwPJHJbja1BovxKBk4YJxiz8OSO68QAIEfxuPTpj5eZz7KEFtFmBIVaVmxBDe4b8Tpl01C2rek7xgPzXaoURvh7CQVnVmJL00DTWKvyOmUOQQW901XEcgcJ7VWgfIvxhIMuXEXXtVDGNowmEc9JQXXYHVlGuN5QicSbApkwwqRZI7TQ4lsS66zCfqomIIJyBNRJpl+8sGwsa2J2h6fEkAD77J9zdUgIKXMFamHbvRadCKMZNIbMrkOC7PuOjZdSiWKh5A8FSjzkv3PlN2hRDqkaODEoodp5pTQeBtNe37+uAMOuHNfsZXlwvfMgCZjiZJ9HQNSLhJBUq7/IvT/EzszUk4HPTj/WFSbT1YrrkDi+zrB20ZDY9lZFWxN1hlYQoNcanDAAWPmw/yW1+8DroL5WIMGsXX3WFGOG7eWB1GHgFQsziAeRQl78u1qOvsRMN08+GrkASBJwqwy5l7xCesUKqbz3O0QA/dwzzsWIDvFPavZpjqMBSjRTurQLFahAaGmdY0BX/Ii+s2+OxfaHQIa1lgucm0P7GPKeZvLX/8boO01Onr/87ra+NX7ABvQb+SXvwsg+Bm5CziWB6DMKDKRD/KQjHxpjIY35UwSEW7G4ixux7ufizXttthHfPJWd/rWFhfYigFhVLgIPCR12smwFVuZwM7ujvY2CIM0X4E0dsX9uVHkgYmqRIdNf5vshpmRuIcHsXZpTJP/tD7zQM6m214c5xkJSfAVIaD7WzRYS4eVL+R3z4u+6n5p6FjuWSjSzuEffUai3HCWjes4JbtDSjIwoG0tOMtBukgPbreH+pjXcvnhU+1QhCV2aIdG6C3FmaI5Uoo/mthJyiFAThwtOpxQ5YkdsRunqVVEFYZfMNEn4Ig2clCFrLOm46JB2wPcLGP2MoH5RqajYzQ6IV8IXIFQVzG0C7HoHsBkVp+GrpnH6N0FCKR+fpbGjigM2lLf4pYBhChUY4ao9hvV1hd8ikS6QoasvDLPytBBa1YAwbSa8d7YdwO6fXfQqetfS8S9gbHD0zxazw5p9Lp5fXFmajDNkD2voYNMzOHJMMHG/49pWV2", + ) + """ + _response = self._raw_client.import_( + brand_id, evp_id=evp_id, vetting_id=vetting_id, vetting_token=vetting_token, request_options=request_options + ) + return _response.data + + +class AsyncBrandVettingsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBrandVettingsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawBrandVettingsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBrandVettingsClient + """ + return self._raw_client + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcBrandVetting]: + """ + Returns the external vettings for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcBrandVetting] + Returns the list of external vettings. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_vettings.list( + brand_id="B6AI7PA", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(brand_id, request_options=request_options) + return _response.data + + async def create( + self, brand_id: str, *, evp_id: str, vetting_class: str, request_options: typing.Optional[RequestOptions] = None + ) -> TenDlcBrandVetting: + """ + Requests external vetting for a 10DLC Brand. Supported providers: `AEGIS`, `CV`, `WMC`. Supported classes: `STANDARD`, `ENHANCED`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider to perform the vetting. + + vetting_class : str + Class of vetting to request, such as `STANDARD` or `ENHANCED`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TenDlcBrandVetting + Returns the requested external vetting. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_vettings.create( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_class="STANDARD", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + brand_id, evp_id=evp_id, vetting_class=vetting_class, request_options=request_options + ) + return _response.data + + async def import_( + self, + brand_id: str, + *, + evp_id: str, + vetting_id: str, + vetting_token: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> TenDlcBrandVetting: + """ + Imports an existing external vetting record into the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider that issued the vetting. + + vetting_id : str + Unique identifier of the vetting request to import. + + vetting_token : str + Token issued by the vetting provider that uniquely identifies the vetting result to import. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TenDlcBrandVetting + Returns the imported external vetting. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brand_vettings.import_( + brand_id="B6AI7PA", + evp_id="AEGIS", + vetting_id="13d8e00c-3cb4-4dc0-9e26-d5057fa938d9", + vetting_token="3oDcE1vq8OR43claMa6Thu/7V4vzZywAfKRgiJnXDjlw+08wpWbGqOssAXKgeZibHCLaGgXvU/yPb7kISeeb5qGdisGRLdhPnSNpvRR82RnCWYNpTp92orlJWjTJU8ZGmNxL5MwK0tt/9SxCha36iTtPV2+4vND8xCPe5suItuQTonG4A3Yi6F1LMqihgwdesRjxJnKqcE7Thcv9ug1NyNPYEZQvPugFj2F2DdU6jFZcOWgXsnE7ucZ+xNaNX9LkF9if3v0hrcviG9L8bUUrpPBGr02txP0i+cPBTLbj4Rq1Ox83R+WUx1gnoXHCIU1ByDGWvQq2Ef4qxGVOwPJHJbja1BovxKBk4YJxiz8OSO68QAIEfxuPTpj5eZz7KEFtFmBIVaVmxBDe4b8Tpl01C2rek7xgPzXaoURvh7CQVnVmJL00DTWKvyOmUOQQW901XEcgcJ7VWgfIvxhIMuXEXXtVDGNowmEc9JQXXYHVlGuN5QicSbApkwwqRZI7TQ4lsS66zCfqomIIJyBNRJpl+8sGwsa2J2h6fEkAD77J9zdUgIKXMFamHbvRadCKMZNIbMrkOC7PuOjZdSiWKh5A8FSjzkv3PlN2hRDqkaODEoodp5pTQeBtNe37+uAMOuHNfsZXlwvfMgCZjiZJ9HQNSLhJBUq7/IvT/EzszUk4HPTj/WFSbT1YrrkDi+zrB20ZDY9lZFWxN1hlYQoNcanDAAWPmw/yW1+8DroL5WIMGsXX3WFGOG7eWB1GHgFQsziAeRQl78u1qOvsRMN08+GrkASBJwqwy5l7xCesUKqbz3O0QA/dwzzsWIDvFPavZpjqMBSjRTurQLFahAaGmdY0BX/Ii+s2+OxfaHQIa1lgucm0P7GPKeZvLX/8boO01Onr/87ra+NX7ABvQb+SXvwsg+Bm5CziWB6DMKDKRD/KQjHxpjIY35UwSEW7G4ixux7ufizXttthHfPJWd/rWFhfYigFhVLgIPCR12smwFVuZwM7ujvY2CIM0X4E0dsX9uVHkgYmqRIdNf5vshpmRuIcHsXZpTJP/tD7zQM6m214c5xkJSfAVIaD7WzRYS4eVL+R3z4u+6n5p6FjuWSjSzuEffUai3HCWjes4JbtDSjIwoG0tOMtBukgPbreH+pjXcvnhU+1QhCV2aIdG6C3FmaI5Uoo/mthJyiFAThwtOpxQ5YkdsRunqVVEFYZfMNEn4Ig2clCFrLOm46JB2wPcLGP2MoH5RqajYzQ6IV8IXIFQVzG0C7HoHsBkVp+GrpnH6N0FCKR+fpbGjigM2lLf4pYBhChUY4ao9hvV1hd8ikS6QoasvDLPytBBa1YAwbSa8d7YdwO6fXfQqetfS8S9gbHD0zxazw5p9Lp5fXFmajDNkD2voYNMzOHJMMHG/49pWV2", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.import_( + brand_id, evp_id=evp_id, vetting_id=vetting_id, vetting_token=vetting_token, request_options=request_options + ) + return _response.data diff --git a/src/wavix/ten_dlc/brand_vettings/raw_client.py b/src/wavix/ten_dlc/brand_vettings/raw_client.py new file mode 100644 index 0000000..c8c7512 --- /dev/null +++ b/src/wavix/ten_dlc/brand_vettings/raw_client.py @@ -0,0 +1,518 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.ten_dlc_brand_vetting import TenDlcBrandVetting +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBrandVettingsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[TenDlcBrandVetting]]: + """ + Returns the external vettings for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[TenDlcBrandVetting]] + Returns the list of external vettings. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandVetting], + parse_obj_as( + type_=typing.List[TenDlcBrandVetting], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, brand_id: str, *, evp_id: str, vetting_class: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TenDlcBrandVetting]: + """ + Requests external vetting for a 10DLC Brand. Supported providers: `AEGIS`, `CV`, `WMC`. Supported classes: `STANDARD`, `ENHANCED`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider to perform the vetting. + + vetting_class : str + Class of vetting to request, such as `STANDARD` or `ENHANCED`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TenDlcBrandVetting] + Returns the requested external vetting. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="POST", + json={ + "evp_id": evp_id, + "vetting_class": vetting_class, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TenDlcBrandVetting, + parse_obj_as( + type_=TenDlcBrandVetting, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def import_( + self, + brand_id: str, + *, + evp_id: str, + vetting_id: str, + vetting_token: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TenDlcBrandVetting]: + """ + Imports an existing external vetting record into the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider that issued the vetting. + + vetting_id : str + Unique identifier of the vetting request to import. + + vetting_token : str + Token issued by the vetting provider that uniquely identifies the vetting result to import. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TenDlcBrandVetting] + Returns the imported external vetting. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="PUT", + json={ + "evp_id": evp_id, + "vetting_id": vetting_id, + "vetting_token": vetting_token, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TenDlcBrandVetting, + parse_obj_as( + type_=TenDlcBrandVetting, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawBrandVettingsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[TenDlcBrandVetting]]: + """ + Returns the external vettings for the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[TenDlcBrandVetting]] + Returns the list of external vettings. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcBrandVetting], + parse_obj_as( + type_=typing.List[TenDlcBrandVetting], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, brand_id: str, *, evp_id: str, vetting_class: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TenDlcBrandVetting]: + """ + Requests external vetting for a 10DLC Brand. Supported providers: `AEGIS`, `CV`, `WMC`. Supported classes: `STANDARD`, `ENHANCED`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider to perform the vetting. + + vetting_class : str + Class of vetting to request, such as `STANDARD` or `ENHANCED`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TenDlcBrandVetting] + Returns the requested external vetting. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="POST", + json={ + "evp_id": evp_id, + "vetting_class": vetting_class, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TenDlcBrandVetting, + parse_obj_as( + type_=TenDlcBrandVetting, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def import_( + self, + brand_id: str, + *, + evp_id: str, + vetting_id: str, + vetting_token: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TenDlcBrandVetting]: + """ + Imports an existing external vetting record into the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + evp_id : str + Code identifying the external vetting provider that issued the vetting. + + vetting_id : str + Unique identifier of the vetting request to import. + + vetting_token : str + Token issued by the vetting provider that uniquely identifies the vetting result to import. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TenDlcBrandVetting] + Returns the imported external vetting. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/vettings", + method="PUT", + json={ + "evp_id": evp_id, + "vetting_id": vetting_id, + "vetting_token": vetting_token, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TenDlcBrandVetting, + parse_obj_as( + type_=TenDlcBrandVetting, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/brands/__init__.py b/src/wavix/ten_dlc/brands/__init__.py new file mode 100644 index 0000000..86bb5a4 --- /dev/null +++ b/src/wavix/ten_dlc/brands/__init__.py @@ -0,0 +1,85 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateBrandsResponse, + CreateBrandsResponseEntityType, + CreateBrandsResponseStatus, + DeleteBrandsResponse, + GetBrandsResponse, + GetBrandsResponseEntityType, + GetBrandsResponseStatus, + ListBrandsResponse, + ListBrandsResponsePagination, + QualifyUsecaseBrandsRequestUseCase, + QualifyUsecaseBrandsResponse, + TenDlcBrandUpdateRequestEntityType, + TenDlcBrandUpdateRequestVertical, + UpdateBrandsResponse, + UpdateBrandsResponseEntityType, + UpdateBrandsResponseStatus, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateBrandsResponse": ".types", + "CreateBrandsResponseEntityType": ".types", + "CreateBrandsResponseStatus": ".types", + "DeleteBrandsResponse": ".types", + "GetBrandsResponse": ".types", + "GetBrandsResponseEntityType": ".types", + "GetBrandsResponseStatus": ".types", + "ListBrandsResponse": ".types", + "ListBrandsResponsePagination": ".types", + "QualifyUsecaseBrandsRequestUseCase": ".types", + "QualifyUsecaseBrandsResponse": ".types", + "TenDlcBrandUpdateRequestEntityType": ".types", + "TenDlcBrandUpdateRequestVertical": ".types", + "UpdateBrandsResponse": ".types", + "UpdateBrandsResponseEntityType": ".types", + "UpdateBrandsResponseStatus": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateBrandsResponse", + "CreateBrandsResponseEntityType", + "CreateBrandsResponseStatus", + "DeleteBrandsResponse", + "GetBrandsResponse", + "GetBrandsResponseEntityType", + "GetBrandsResponseStatus", + "ListBrandsResponse", + "ListBrandsResponsePagination", + "QualifyUsecaseBrandsRequestUseCase", + "QualifyUsecaseBrandsResponse", + "TenDlcBrandUpdateRequestEntityType", + "TenDlcBrandUpdateRequestVertical", + "UpdateBrandsResponse", + "UpdateBrandsResponseEntityType", + "UpdateBrandsResponseStatus", +] diff --git a/src/wavix/ten_dlc/brands/client.py b/src/wavix/ten_dlc/brands/client.py new file mode 100644 index 0000000..f9d22d1 --- /dev/null +++ b/src/wavix/ten_dlc/brands/client.py @@ -0,0 +1,870 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.ten_dlc_brand_create_request import TenDlcBrandCreateRequest +from .raw_client import AsyncRawBrandsClient, RawBrandsClient +from .types.create_brands_response import CreateBrandsResponse +from .types.delete_brands_response import DeleteBrandsResponse +from .types.get_brands_response import GetBrandsResponse +from .types.list_brands_response import ListBrandsResponse +from .types.qualify_usecase_brands_request_use_case import QualifyUsecaseBrandsRequestUseCase +from .types.qualify_usecase_brands_response import QualifyUsecaseBrandsResponse +from .types.ten_dlc_brand_update_request_entity_type import TenDlcBrandUpdateRequestEntityType +from .types.ten_dlc_brand_update_request_vertical import TenDlcBrandUpdateRequestVertical +from .types.update_brands_response import UpdateBrandsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class BrandsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawBrandsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawBrandsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawBrandsClient + """ + return self._raw_client + + def list( + self, + *, + dba_name: typing.Optional[str] = None, + company_name: typing.Optional[str] = None, + entity_type: typing.Optional[str] = None, + status: typing.Optional[str] = None, + country: typing.Optional[str] = None, + show_deleted: typing.Optional[bool] = None, + ein_taxid: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[str] = None, + created_after: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListBrandsResponse: + """ + Returns a paginated list of 10DLC Brands for the authenticated account, filtered by date, name, legal name, and status. + + Parameters + ---------- + dba_name : typing.Optional[str] + Filters Brands by `dba_name` (doing-business-as name). Matches partial values. + + company_name : typing.Optional[str] + Filters Brands by `company_name` (registered legal name). Matches partial values. + + entity_type : typing.Optional[str] + Filters Brands by business entity type, such as `PRIVATE_PROFIT`. + + status : typing.Optional[str] + Filters Brands by identity verification status, such as `VERIFIED`. + + country : typing.Optional[str] + Filters Brands by registration country, as an ISO 3166-1 alpha-2 code (e.g., `US`). + + show_deleted : typing.Optional[bool] + When `true`, includes deleted Brands in the results. Default `false`. + + ein_taxid : typing.Optional[str] + Filters Brands by their Employer Identification Number (EIN) or tax ID. + + mock : typing.Optional[bool] + When `true`, returns only mock Brands used for testing. Default `false`. + + created_before : typing.Optional[str] + Returns brands created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[str] + Returns brands created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListBrandsResponse + Returns a paginated list of 10DLC Brands. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.list( + dba_name="Brand", + company_name="Company", + entity_type="PRIVATE_PROFIT", + status="VERIFIED", + country="US", + show_deleted=False, + ein_taxid="999999999", + mock=False, + created_before="2024-08-22", + created_after="2024-08-22", + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + dba_name=dba_name, + company_name=company_name, + entity_type=entity_type, + status=status, + country=country, + show_deleted=show_deleted, + ein_taxid=ein_taxid, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def create( + self, *, request: TenDlcBrandCreateRequest, request_options: typing.Optional[RequestOptions] = None + ) -> CreateBrandsResponse: + """ + Registers a 10DLC Brand. TCR automatically verifies the brand identity. Only brands with `VERIFIED` or `VETTED_VERIFIED` identity status can register 10DLC Campaigns. + + Parameters + ---------- + request : TenDlcBrandCreateRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandsResponse + Returns the registered 10DLC Brand. + + Examples + -------- + from wavix import TenDlcBrandCreateRequestZero, Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.create( + request=TenDlcBrandCreateRequestZero(), + ) + """ + _response = self._raw_client.create(request=request, request_options=request_options) + return _response.data + + def get(self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GetBrandsResponse: + """ + Returns the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetBrandsResponse + Returns the 10DLC Brand. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.get( + brand_id="BM20QP9", + ) + """ + _response = self._raw_client.get(brand_id, request_options=request_options) + return _response.data + + def update( + self, + brand_id: str, + *, + dba_name: typing.Optional[str] = OMIT, + company_name: typing.Optional[str] = OMIT, + entity_type: typing.Optional[TenDlcBrandUpdateRequestEntityType] = OMIT, + vertical: typing.Optional[TenDlcBrandUpdateRequestVertical] = OMIT, + ein_taxid: typing.Optional[str] = OMIT, + ein_taxid_country: typing.Optional[str] = OMIT, + website: typing.Optional[str] = OMIT, + stock_symbol: typing.Optional[str] = OMIT, + stock_exchange: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone_number: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + street_address: typing.Optional[str] = OMIT, + city: typing.Optional[str] = OMIT, + state_or_province: typing.Optional[str] = OMIT, + zip: typing.Optional[str] = OMIT, + country: typing.Optional[str] = OMIT, + mock: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> UpdateBrandsResponse: + """ + Updates the 10DLC Brand identified by `brand_id`. Changing identity fields, including `ein_taxid`, `ein_taxid_country`, and `entity_type`, resets the Brand status to `UNVERIFIED` and triggers automatic re-submission. Brands in `VETTED_VERIFIED` status or with active Campaigns cannot be updated. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + dba_name : typing.Optional[str] + Brand name or DBA + + company_name : typing.Optional[str] + Legal name of the company + + entity_type : typing.Optional[TenDlcBrandUpdateRequestEntityType] + Legal entity type of the company. One of `PRIVATE_PROFIT` (privately held for-profit company), `PUBLIC_PROFIT` (publicly traded for-profit company), `NON_PROFIT` (non-profit organization), or `GOVERNMENT` (government entity). + + vertical : typing.Optional[TenDlcBrandUpdateRequestVertical] + Business segment the Brand operates in. One of: + + - `HEALTHCARE` — healthcare. + - `PROFESSIONAL` — professional services. + - `RETAIL` — retail. + - `TECHNOLOGY` — technology. + - `EDUCATION` — education. + - `FINANCIAL` — financial services. + - `NON_PROFIT` — non-profit organizations. + - `GOVERNMENT` — government entities. + - `OTHER` — any segment not listed above. + + ein_taxid : typing.Optional[str] + IRS Employee Identification Number (EIN) for US-based or foreign companies with EIN. The numeric portion of Tax ID for companies incorporated in other countries. + + ein_taxid_country : typing.Optional[str] + 2-letter ISO country code of the Tax ID issuing country + + website : typing.Optional[str] + The website of the business + + stock_symbol : typing.Optional[str] + The stock symbol of the Brand. For PUBLIC_PROFIT Brands only. + + stock_exchange : typing.Optional[str] + The stock exchange code. For PUBLIC_PROFIT Brands only. + + first_name : typing.Optional[str] + The first name of the business contact + + last_name : typing.Optional[str] + The last name of the business contact + + phone_number : typing.Optional[str] + The support contact telephone in E.164 format + + email : typing.Optional[str] + The email address of the support contact + + street_address : typing.Optional[str] + Street name and house number + + city : typing.Optional[str] + The city name + + state_or_province : typing.Optional[str] + State or province. For the United States, use 2 character codes. + + zip : typing.Optional[str] + The business zip or postal code + + country : typing.Optional[str] + 2-letter ISO country code the business address + + mock : typing.Optional[bool] + Mock flag for testing (optional, defaults to false) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UpdateBrandsResponse + Returns the updated 10DLC Brand. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.update( + brand_id="BM20QP9", + ) + """ + _response = self._raw_client.update( + brand_id, + dba_name=dba_name, + company_name=company_name, + entity_type=entity_type, + vertical=vertical, + ein_taxid=ein_taxid, + ein_taxid_country=ein_taxid_country, + website=website, + stock_symbol=stock_symbol, + stock_exchange=stock_exchange, + first_name=first_name, + last_name=last_name, + phone_number=phone_number, + email=email, + street_address=street_address, + city=city, + state_or_province=state_or_province, + zip=zip, + country=country, + mock=mock, + request_options=request_options, + ) + return _response.data + + def delete(self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DeleteBrandsResponse: + """ + Deletes a 10DLC Brand. Brands with active campaigns cannot be deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteBrandsResponse + Returns a success confirmation. The 10DLC Brand is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.delete( + brand_id="BM20QP9", + ) + """ + _response = self._raw_client.delete(brand_id, request_options=request_options) + return _response.data + + def qualify_usecase( + self, + brand_id: str, + use_case: QualifyUsecaseBrandsRequestUseCase, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> QualifyUsecaseBrandsResponse: + """ + Returns the qualification results for a 10DLC Brand use case. Includes MNO-specific attributes, restrictions, and fees. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + use_case : QualifyUsecaseBrandsRequestUseCase + Name of the use case to qualify the Brand for. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + QualifyUsecaseBrandsResponse + Returns the use case qualification results for the Brand. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.brands.qualify_usecase( + brand_id="BMQFB7X", + use_case="AGENTS_FRANCHISES", + ) + """ + _response = self._raw_client.qualify_usecase(brand_id, use_case, request_options=request_options) + return _response.data + + +class AsyncBrandsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawBrandsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawBrandsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawBrandsClient + """ + return self._raw_client + + async def list( + self, + *, + dba_name: typing.Optional[str] = None, + company_name: typing.Optional[str] = None, + entity_type: typing.Optional[str] = None, + status: typing.Optional[str] = None, + country: typing.Optional[str] = None, + show_deleted: typing.Optional[bool] = None, + ein_taxid: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[str] = None, + created_after: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListBrandsResponse: + """ + Returns a paginated list of 10DLC Brands for the authenticated account, filtered by date, name, legal name, and status. + + Parameters + ---------- + dba_name : typing.Optional[str] + Filters Brands by `dba_name` (doing-business-as name). Matches partial values. + + company_name : typing.Optional[str] + Filters Brands by `company_name` (registered legal name). Matches partial values. + + entity_type : typing.Optional[str] + Filters Brands by business entity type, such as `PRIVATE_PROFIT`. + + status : typing.Optional[str] + Filters Brands by identity verification status, such as `VERIFIED`. + + country : typing.Optional[str] + Filters Brands by registration country, as an ISO 3166-1 alpha-2 code (e.g., `US`). + + show_deleted : typing.Optional[bool] + When `true`, includes deleted Brands in the results. Default `false`. + + ein_taxid : typing.Optional[str] + Filters Brands by their Employer Identification Number (EIN) or tax ID. + + mock : typing.Optional[bool] + When `true`, returns only mock Brands used for testing. Default `false`. + + created_before : typing.Optional[str] + Returns brands created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[str] + Returns brands created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListBrandsResponse + Returns a paginated list of 10DLC Brands. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.list( + dba_name="Brand", + company_name="Company", + entity_type="PRIVATE_PROFIT", + status="VERIFIED", + country="US", + show_deleted=False, + ein_taxid="999999999", + mock=False, + created_before="2024-08-22", + created_after="2024-08-22", + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + dba_name=dba_name, + company_name=company_name, + entity_type=entity_type, + status=status, + country=country, + show_deleted=show_deleted, + ein_taxid=ein_taxid, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def create( + self, *, request: TenDlcBrandCreateRequest, request_options: typing.Optional[RequestOptions] = None + ) -> CreateBrandsResponse: + """ + Registers a 10DLC Brand. TCR automatically verifies the brand identity. Only brands with `VERIFIED` or `VETTED_VERIFIED` identity status can register 10DLC Campaigns. + + Parameters + ---------- + request : TenDlcBrandCreateRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateBrandsResponse + Returns the registered 10DLC Brand. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix, TenDlcBrandCreateRequestZero + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.create( + request=TenDlcBrandCreateRequestZero(), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create(request=request, request_options=request_options) + return _response.data + + async def get(self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GetBrandsResponse: + """ + Returns the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetBrandsResponse + Returns the 10DLC Brand. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.get( + brand_id="BM20QP9", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(brand_id, request_options=request_options) + return _response.data + + async def update( + self, + brand_id: str, + *, + dba_name: typing.Optional[str] = OMIT, + company_name: typing.Optional[str] = OMIT, + entity_type: typing.Optional[TenDlcBrandUpdateRequestEntityType] = OMIT, + vertical: typing.Optional[TenDlcBrandUpdateRequestVertical] = OMIT, + ein_taxid: typing.Optional[str] = OMIT, + ein_taxid_country: typing.Optional[str] = OMIT, + website: typing.Optional[str] = OMIT, + stock_symbol: typing.Optional[str] = OMIT, + stock_exchange: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone_number: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + street_address: typing.Optional[str] = OMIT, + city: typing.Optional[str] = OMIT, + state_or_province: typing.Optional[str] = OMIT, + zip: typing.Optional[str] = OMIT, + country: typing.Optional[str] = OMIT, + mock: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> UpdateBrandsResponse: + """ + Updates the 10DLC Brand identified by `brand_id`. Changing identity fields, including `ein_taxid`, `ein_taxid_country`, and `entity_type`, resets the Brand status to `UNVERIFIED` and triggers automatic re-submission. Brands in `VETTED_VERIFIED` status or with active Campaigns cannot be updated. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + dba_name : typing.Optional[str] + Brand name or DBA + + company_name : typing.Optional[str] + Legal name of the company + + entity_type : typing.Optional[TenDlcBrandUpdateRequestEntityType] + Legal entity type of the company. One of `PRIVATE_PROFIT` (privately held for-profit company), `PUBLIC_PROFIT` (publicly traded for-profit company), `NON_PROFIT` (non-profit organization), or `GOVERNMENT` (government entity). + + vertical : typing.Optional[TenDlcBrandUpdateRequestVertical] + Business segment the Brand operates in. One of: + + - `HEALTHCARE` — healthcare. + - `PROFESSIONAL` — professional services. + - `RETAIL` — retail. + - `TECHNOLOGY` — technology. + - `EDUCATION` — education. + - `FINANCIAL` — financial services. + - `NON_PROFIT` — non-profit organizations. + - `GOVERNMENT` — government entities. + - `OTHER` — any segment not listed above. + + ein_taxid : typing.Optional[str] + IRS Employee Identification Number (EIN) for US-based or foreign companies with EIN. The numeric portion of Tax ID for companies incorporated in other countries. + + ein_taxid_country : typing.Optional[str] + 2-letter ISO country code of the Tax ID issuing country + + website : typing.Optional[str] + The website of the business + + stock_symbol : typing.Optional[str] + The stock symbol of the Brand. For PUBLIC_PROFIT Brands only. + + stock_exchange : typing.Optional[str] + The stock exchange code. For PUBLIC_PROFIT Brands only. + + first_name : typing.Optional[str] + The first name of the business contact + + last_name : typing.Optional[str] + The last name of the business contact + + phone_number : typing.Optional[str] + The support contact telephone in E.164 format + + email : typing.Optional[str] + The email address of the support contact + + street_address : typing.Optional[str] + Street name and house number + + city : typing.Optional[str] + The city name + + state_or_province : typing.Optional[str] + State or province. For the United States, use 2 character codes. + + zip : typing.Optional[str] + The business zip or postal code + + country : typing.Optional[str] + 2-letter ISO country code the business address + + mock : typing.Optional[bool] + Mock flag for testing (optional, defaults to false) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UpdateBrandsResponse + Returns the updated 10DLC Brand. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.update( + brand_id="BM20QP9", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + brand_id, + dba_name=dba_name, + company_name=company_name, + entity_type=entity_type, + vertical=vertical, + ein_taxid=ein_taxid, + ein_taxid_country=ein_taxid_country, + website=website, + stock_symbol=stock_symbol, + stock_exchange=stock_exchange, + first_name=first_name, + last_name=last_name, + phone_number=phone_number, + email=email, + street_address=street_address, + city=city, + state_or_province=state_or_province, + zip=zip, + country=country, + mock=mock, + request_options=request_options, + ) + return _response.data + + async def delete( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteBrandsResponse: + """ + Deletes a 10DLC Brand. Brands with active campaigns cannot be deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteBrandsResponse + Returns a success confirmation. The 10DLC Brand is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.delete( + brand_id="BM20QP9", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(brand_id, request_options=request_options) + return _response.data + + async def qualify_usecase( + self, + brand_id: str, + use_case: QualifyUsecaseBrandsRequestUseCase, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> QualifyUsecaseBrandsResponse: + """ + Returns the qualification results for a 10DLC Brand use case. Includes MNO-specific attributes, restrictions, and fees. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + use_case : QualifyUsecaseBrandsRequestUseCase + Name of the use case to qualify the Brand for. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + QualifyUsecaseBrandsResponse + Returns the use case qualification results for the Brand. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.brands.qualify_usecase( + brand_id="BMQFB7X", + use_case="AGENTS_FRANCHISES", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.qualify_usecase(brand_id, use_case, request_options=request_options) + return _response.data diff --git a/src/wavix/ten_dlc/brands/raw_client.py b/src/wavix/ten_dlc/brands/raw_client.py new file mode 100644 index 0000000..6ed0068 --- /dev/null +++ b/src/wavix/ten_dlc/brands/raw_client.py @@ -0,0 +1,1263 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unprocessable_entity_error import UnprocessableEntityError +from ...types.ten_dlc_brand_create_request import TenDlcBrandCreateRequest +from .types.create_brands_response import CreateBrandsResponse +from .types.delete_brands_response import DeleteBrandsResponse +from .types.get_brands_response import GetBrandsResponse +from .types.list_brands_response import ListBrandsResponse +from .types.qualify_usecase_brands_request_use_case import QualifyUsecaseBrandsRequestUseCase +from .types.qualify_usecase_brands_response import QualifyUsecaseBrandsResponse +from .types.ten_dlc_brand_update_request_entity_type import TenDlcBrandUpdateRequestEntityType +from .types.ten_dlc_brand_update_request_vertical import TenDlcBrandUpdateRequestVertical +from .types.update_brands_response import UpdateBrandsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawBrandsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + dba_name: typing.Optional[str] = None, + company_name: typing.Optional[str] = None, + entity_type: typing.Optional[str] = None, + status: typing.Optional[str] = None, + country: typing.Optional[str] = None, + show_deleted: typing.Optional[bool] = None, + ein_taxid: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[str] = None, + created_after: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListBrandsResponse]: + """ + Returns a paginated list of 10DLC Brands for the authenticated account, filtered by date, name, legal name, and status. + + Parameters + ---------- + dba_name : typing.Optional[str] + Filters Brands by `dba_name` (doing-business-as name). Matches partial values. + + company_name : typing.Optional[str] + Filters Brands by `company_name` (registered legal name). Matches partial values. + + entity_type : typing.Optional[str] + Filters Brands by business entity type, such as `PRIVATE_PROFIT`. + + status : typing.Optional[str] + Filters Brands by identity verification status, such as `VERIFIED`. + + country : typing.Optional[str] + Filters Brands by registration country, as an ISO 3166-1 alpha-2 code (e.g., `US`). + + show_deleted : typing.Optional[bool] + When `true`, includes deleted Brands in the results. Default `false`. + + ein_taxid : typing.Optional[str] + Filters Brands by their Employer Identification Number (EIN) or tax ID. + + mock : typing.Optional[bool] + When `true`, returns only mock Brands used for testing. Default `false`. + + created_before : typing.Optional[str] + Returns brands created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[str] + Returns brands created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListBrandsResponse] + Returns a paginated list of 10DLC Brands. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/brands", + method="GET", + params={ + "dba_name": dba_name, + "company_name": company_name, + "entity_type": entity_type, + "status": status, + "country": country, + "show_deleted": show_deleted, + "ein_taxid": ein_taxid, + "mock": mock, + "created_before": created_before, + "created_after": created_after, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListBrandsResponse, + parse_obj_as( + type_=ListBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, *, request: TenDlcBrandCreateRequest, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CreateBrandsResponse]: + """ + Registers a 10DLC Brand. TCR automatically verifies the brand identity. Only brands with `VERIFIED` or `VETTED_VERIFIED` identity status can register 10DLC Campaigns. + + Parameters + ---------- + request : TenDlcBrandCreateRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateBrandsResponse] + Returns the registered 10DLC Brand. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/brands", + method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=TenDlcBrandCreateRequest, direction="write" + ), + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandsResponse, + parse_obj_as( + type_=CreateBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[GetBrandsResponse]: + """ + Returns the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetBrandsResponse] + Returns the 10DLC Brand. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetBrandsResponse, + parse_obj_as( + type_=GetBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + brand_id: str, + *, + dba_name: typing.Optional[str] = OMIT, + company_name: typing.Optional[str] = OMIT, + entity_type: typing.Optional[TenDlcBrandUpdateRequestEntityType] = OMIT, + vertical: typing.Optional[TenDlcBrandUpdateRequestVertical] = OMIT, + ein_taxid: typing.Optional[str] = OMIT, + ein_taxid_country: typing.Optional[str] = OMIT, + website: typing.Optional[str] = OMIT, + stock_symbol: typing.Optional[str] = OMIT, + stock_exchange: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone_number: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + street_address: typing.Optional[str] = OMIT, + city: typing.Optional[str] = OMIT, + state_or_province: typing.Optional[str] = OMIT, + zip: typing.Optional[str] = OMIT, + country: typing.Optional[str] = OMIT, + mock: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[UpdateBrandsResponse]: + """ + Updates the 10DLC Brand identified by `brand_id`. Changing identity fields, including `ein_taxid`, `ein_taxid_country`, and `entity_type`, resets the Brand status to `UNVERIFIED` and triggers automatic re-submission. Brands in `VETTED_VERIFIED` status or with active Campaigns cannot be updated. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + dba_name : typing.Optional[str] + Brand name or DBA + + company_name : typing.Optional[str] + Legal name of the company + + entity_type : typing.Optional[TenDlcBrandUpdateRequestEntityType] + Legal entity type of the company. One of `PRIVATE_PROFIT` (privately held for-profit company), `PUBLIC_PROFIT` (publicly traded for-profit company), `NON_PROFIT` (non-profit organization), or `GOVERNMENT` (government entity). + + vertical : typing.Optional[TenDlcBrandUpdateRequestVertical] + Business segment the Brand operates in. One of: + + - `HEALTHCARE` — healthcare. + - `PROFESSIONAL` — professional services. + - `RETAIL` — retail. + - `TECHNOLOGY` — technology. + - `EDUCATION` — education. + - `FINANCIAL` — financial services. + - `NON_PROFIT` — non-profit organizations. + - `GOVERNMENT` — government entities. + - `OTHER` — any segment not listed above. + + ein_taxid : typing.Optional[str] + IRS Employee Identification Number (EIN) for US-based or foreign companies with EIN. The numeric portion of Tax ID for companies incorporated in other countries. + + ein_taxid_country : typing.Optional[str] + 2-letter ISO country code of the Tax ID issuing country + + website : typing.Optional[str] + The website of the business + + stock_symbol : typing.Optional[str] + The stock symbol of the Brand. For PUBLIC_PROFIT Brands only. + + stock_exchange : typing.Optional[str] + The stock exchange code. For PUBLIC_PROFIT Brands only. + + first_name : typing.Optional[str] + The first name of the business contact + + last_name : typing.Optional[str] + The last name of the business contact + + phone_number : typing.Optional[str] + The support contact telephone in E.164 format + + email : typing.Optional[str] + The email address of the support contact + + street_address : typing.Optional[str] + Street name and house number + + city : typing.Optional[str] + The city name + + state_or_province : typing.Optional[str] + State or province. For the United States, use 2 character codes. + + zip : typing.Optional[str] + The business zip or postal code + + country : typing.Optional[str] + 2-letter ISO country code the business address + + mock : typing.Optional[bool] + Mock flag for testing (optional, defaults to false) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UpdateBrandsResponse] + Returns the updated 10DLC Brand. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="PUT", + json={ + "dba_name": dba_name, + "company_name": company_name, + "entity_type": entity_type, + "vertical": vertical, + "ein_taxid": ein_taxid, + "ein_taxid_country": ein_taxid_country, + "website": website, + "stock_symbol": stock_symbol, + "stock_exchange": stock_exchange, + "first_name": first_name, + "last_name": last_name, + "phone_number": phone_number, + "email": email, + "street_address": street_address, + "city": city, + "state_or_province": state_or_province, + "zip": zip, + "country": country, + "mock": mock, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UpdateBrandsResponse, + parse_obj_as( + type_=UpdateBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteBrandsResponse]: + """ + Deletes a 10DLC Brand. Brands with active campaigns cannot be deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteBrandsResponse] + Returns a success confirmation. The 10DLC Brand is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteBrandsResponse, + parse_obj_as( + type_=DeleteBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def qualify_usecase( + self, + brand_id: str, + use_case: QualifyUsecaseBrandsRequestUseCase, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[QualifyUsecaseBrandsResponse]: + """ + Returns the qualification results for a 10DLC Brand use case. Includes MNO-specific attributes, restrictions, and fees. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + use_case : QualifyUsecaseBrandsRequestUseCase + Name of the use case to qualify the Brand for. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[QualifyUsecaseBrandsResponse] + Returns the use case qualification results for the Brand. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/usecases/{encode_path_param(use_case)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + QualifyUsecaseBrandsResponse, + parse_obj_as( + type_=QualifyUsecaseBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawBrandsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + dba_name: typing.Optional[str] = None, + company_name: typing.Optional[str] = None, + entity_type: typing.Optional[str] = None, + status: typing.Optional[str] = None, + country: typing.Optional[str] = None, + show_deleted: typing.Optional[bool] = None, + ein_taxid: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[str] = None, + created_after: typing.Optional[str] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListBrandsResponse]: + """ + Returns a paginated list of 10DLC Brands for the authenticated account, filtered by date, name, legal name, and status. + + Parameters + ---------- + dba_name : typing.Optional[str] + Filters Brands by `dba_name` (doing-business-as name). Matches partial values. + + company_name : typing.Optional[str] + Filters Brands by `company_name` (registered legal name). Matches partial values. + + entity_type : typing.Optional[str] + Filters Brands by business entity type, such as `PRIVATE_PROFIT`. + + status : typing.Optional[str] + Filters Brands by identity verification status, such as `VERIFIED`. + + country : typing.Optional[str] + Filters Brands by registration country, as an ISO 3166-1 alpha-2 code (e.g., `US`). + + show_deleted : typing.Optional[bool] + When `true`, includes deleted Brands in the results. Default `false`. + + ein_taxid : typing.Optional[str] + Filters Brands by their Employer Identification Number (EIN) or tax ID. + + mock : typing.Optional[bool] + When `true`, returns only mock Brands used for testing. Default `false`. + + created_before : typing.Optional[str] + Returns brands created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[str] + Returns brands created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListBrandsResponse] + Returns a paginated list of 10DLC Brands. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/brands", + method="GET", + params={ + "dba_name": dba_name, + "company_name": company_name, + "entity_type": entity_type, + "status": status, + "country": country, + "show_deleted": show_deleted, + "ein_taxid": ein_taxid, + "mock": mock, + "created_before": created_before, + "created_after": created_after, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListBrandsResponse, + parse_obj_as( + type_=ListBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, *, request: TenDlcBrandCreateRequest, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreateBrandsResponse]: + """ + Registers a 10DLC Brand. TCR automatically verifies the brand identity. Only brands with `VERIFIED` or `VETTED_VERIFIED` identity status can register 10DLC Campaigns. + + Parameters + ---------- + request : TenDlcBrandCreateRequest + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateBrandsResponse] + Returns the registered 10DLC Brand. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/brands", + method="POST", + json=convert_and_respect_annotation_metadata( + object_=request, annotation=TenDlcBrandCreateRequest, direction="write" + ), + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateBrandsResponse, + parse_obj_as( + type_=CreateBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetBrandsResponse]: + """ + Returns the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetBrandsResponse] + Returns the 10DLC Brand. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetBrandsResponse, + parse_obj_as( + type_=GetBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + brand_id: str, + *, + dba_name: typing.Optional[str] = OMIT, + company_name: typing.Optional[str] = OMIT, + entity_type: typing.Optional[TenDlcBrandUpdateRequestEntityType] = OMIT, + vertical: typing.Optional[TenDlcBrandUpdateRequestVertical] = OMIT, + ein_taxid: typing.Optional[str] = OMIT, + ein_taxid_country: typing.Optional[str] = OMIT, + website: typing.Optional[str] = OMIT, + stock_symbol: typing.Optional[str] = OMIT, + stock_exchange: typing.Optional[str] = OMIT, + first_name: typing.Optional[str] = OMIT, + last_name: typing.Optional[str] = OMIT, + phone_number: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + street_address: typing.Optional[str] = OMIT, + city: typing.Optional[str] = OMIT, + state_or_province: typing.Optional[str] = OMIT, + zip: typing.Optional[str] = OMIT, + country: typing.Optional[str] = OMIT, + mock: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[UpdateBrandsResponse]: + """ + Updates the 10DLC Brand identified by `brand_id`. Changing identity fields, including `ein_taxid`, `ein_taxid_country`, and `entity_type`, resets the Brand status to `UNVERIFIED` and triggers automatic re-submission. Brands in `VETTED_VERIFIED` status or with active Campaigns cannot be updated. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + dba_name : typing.Optional[str] + Brand name or DBA + + company_name : typing.Optional[str] + Legal name of the company + + entity_type : typing.Optional[TenDlcBrandUpdateRequestEntityType] + Legal entity type of the company. One of `PRIVATE_PROFIT` (privately held for-profit company), `PUBLIC_PROFIT` (publicly traded for-profit company), `NON_PROFIT` (non-profit organization), or `GOVERNMENT` (government entity). + + vertical : typing.Optional[TenDlcBrandUpdateRequestVertical] + Business segment the Brand operates in. One of: + + - `HEALTHCARE` — healthcare. + - `PROFESSIONAL` — professional services. + - `RETAIL` — retail. + - `TECHNOLOGY` — technology. + - `EDUCATION` — education. + - `FINANCIAL` — financial services. + - `NON_PROFIT` — non-profit organizations. + - `GOVERNMENT` — government entities. + - `OTHER` — any segment not listed above. + + ein_taxid : typing.Optional[str] + IRS Employee Identification Number (EIN) for US-based or foreign companies with EIN. The numeric portion of Tax ID for companies incorporated in other countries. + + ein_taxid_country : typing.Optional[str] + 2-letter ISO country code of the Tax ID issuing country + + website : typing.Optional[str] + The website of the business + + stock_symbol : typing.Optional[str] + The stock symbol of the Brand. For PUBLIC_PROFIT Brands only. + + stock_exchange : typing.Optional[str] + The stock exchange code. For PUBLIC_PROFIT Brands only. + + first_name : typing.Optional[str] + The first name of the business contact + + last_name : typing.Optional[str] + The last name of the business contact + + phone_number : typing.Optional[str] + The support contact telephone in E.164 format + + email : typing.Optional[str] + The email address of the support contact + + street_address : typing.Optional[str] + Street name and house number + + city : typing.Optional[str] + The city name + + state_or_province : typing.Optional[str] + State or province. For the United States, use 2 character codes. + + zip : typing.Optional[str] + The business zip or postal code + + country : typing.Optional[str] + 2-letter ISO country code the business address + + mock : typing.Optional[bool] + Mock flag for testing (optional, defaults to false) + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UpdateBrandsResponse] + Returns the updated 10DLC Brand. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="PUT", + json={ + "dba_name": dba_name, + "company_name": company_name, + "entity_type": entity_type, + "vertical": vertical, + "ein_taxid": ein_taxid, + "ein_taxid_country": ein_taxid_country, + "website": website, + "stock_symbol": stock_symbol, + "stock_exchange": stock_exchange, + "first_name": first_name, + "last_name": last_name, + "phone_number": phone_number, + "email": email, + "street_address": street_address, + "city": city, + "state_or_province": state_or_province, + "zip": zip, + "country": country, + "mock": mock, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UpdateBrandsResponse, + parse_obj_as( + type_=UpdateBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, brand_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteBrandsResponse]: + """ + Deletes a 10DLC Brand. Brands with active campaigns cannot be deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteBrandsResponse] + Returns a success confirmation. The 10DLC Brand is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteBrandsResponse, + parse_obj_as( + type_=DeleteBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def qualify_usecase( + self, + brand_id: str, + use_case: QualifyUsecaseBrandsRequestUseCase, + *, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[QualifyUsecaseBrandsResponse]: + """ + Returns the qualification results for a 10DLC Brand use case. Includes MNO-specific attributes, restrictions, and fees. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + use_case : QualifyUsecaseBrandsRequestUseCase + Name of the use case to qualify the Brand for. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[QualifyUsecaseBrandsResponse] + Returns the use case qualification results for the Brand. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/usecases/{encode_path_param(use_case)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + QualifyUsecaseBrandsResponse, + parse_obj_as( + type_=QualifyUsecaseBrandsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/brands/types/__init__.py b/src/wavix/ten_dlc/brands/types/__init__.py new file mode 100644 index 0000000..e598915 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/__init__.py @@ -0,0 +1,83 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_brands_response import CreateBrandsResponse + from .create_brands_response_entity_type import CreateBrandsResponseEntityType + from .create_brands_response_status import CreateBrandsResponseStatus + from .delete_brands_response import DeleteBrandsResponse + from .get_brands_response import GetBrandsResponse + from .get_brands_response_entity_type import GetBrandsResponseEntityType + from .get_brands_response_status import GetBrandsResponseStatus + from .list_brands_response import ListBrandsResponse + from .list_brands_response_pagination import ListBrandsResponsePagination + from .qualify_usecase_brands_request_use_case import QualifyUsecaseBrandsRequestUseCase + from .qualify_usecase_brands_response import QualifyUsecaseBrandsResponse + from .ten_dlc_brand_update_request_entity_type import TenDlcBrandUpdateRequestEntityType + from .ten_dlc_brand_update_request_vertical import TenDlcBrandUpdateRequestVertical + from .update_brands_response import UpdateBrandsResponse + from .update_brands_response_entity_type import UpdateBrandsResponseEntityType + from .update_brands_response_status import UpdateBrandsResponseStatus +_dynamic_imports: typing.Dict[str, str] = { + "CreateBrandsResponse": ".create_brands_response", + "CreateBrandsResponseEntityType": ".create_brands_response_entity_type", + "CreateBrandsResponseStatus": ".create_brands_response_status", + "DeleteBrandsResponse": ".delete_brands_response", + "GetBrandsResponse": ".get_brands_response", + "GetBrandsResponseEntityType": ".get_brands_response_entity_type", + "GetBrandsResponseStatus": ".get_brands_response_status", + "ListBrandsResponse": ".list_brands_response", + "ListBrandsResponsePagination": ".list_brands_response_pagination", + "QualifyUsecaseBrandsRequestUseCase": ".qualify_usecase_brands_request_use_case", + "QualifyUsecaseBrandsResponse": ".qualify_usecase_brands_response", + "TenDlcBrandUpdateRequestEntityType": ".ten_dlc_brand_update_request_entity_type", + "TenDlcBrandUpdateRequestVertical": ".ten_dlc_brand_update_request_vertical", + "UpdateBrandsResponse": ".update_brands_response", + "UpdateBrandsResponseEntityType": ".update_brands_response_entity_type", + "UpdateBrandsResponseStatus": ".update_brands_response_status", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateBrandsResponse", + "CreateBrandsResponseEntityType", + "CreateBrandsResponseStatus", + "DeleteBrandsResponse", + "GetBrandsResponse", + "GetBrandsResponseEntityType", + "GetBrandsResponseStatus", + "ListBrandsResponse", + "ListBrandsResponsePagination", + "QualifyUsecaseBrandsRequestUseCase", + "QualifyUsecaseBrandsResponse", + "TenDlcBrandUpdateRequestEntityType", + "TenDlcBrandUpdateRequestVertical", + "UpdateBrandsResponse", + "UpdateBrandsResponseEntityType", + "UpdateBrandsResponseStatus", +] diff --git a/src/wavix/ten_dlc/brands/types/create_brands_response.py b/src/wavix/ten_dlc/brands/types/create_brands_response.py new file mode 100644 index 0000000..ac7177a --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/create_brands_response.py @@ -0,0 +1,143 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .create_brands_response_entity_type import CreateBrandsResponseEntityType +from .create_brands_response_status import CreateBrandsResponseStatus + + +class CreateBrandsResponse(UniversalBaseModel): + """ + Represents a 10DLC brand registered for application-to-person messaging. A Brand identifies the business behind one or more messaging campaigns. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand assigned by the registry. + """ + + dba_name: str = pydantic.Field() + """ + Doing-business-as name, or the public-facing brand name. + """ + + company_name: str = pydantic.Field() + """ + Registered legal name of the company that owns the Brand. + """ + + entity_type: CreateBrandsResponseEntityType = pydantic.Field() + """ + Company entity type. + """ + + vertical: str = pydantic.Field() + """ + Industry vertical the Brand operates in. + """ + + ein_taxid: str = pydantic.Field() + """ + IRS Employer Identification Number (EIN) or other tax ID of the company. + """ + + ein_taxid_country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code where the Tax ID was issued. + """ + + status: CreateBrandsResponseStatus = pydantic.Field() + """ + Brand identity verification status. + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Business website URL. + """ + + stock_symbol: typing.Optional[str] = pydantic.Field(default=None) + """ + Stock ticker symbol of the company. Required for publicly traded companies. + """ + + stock_exchange: typing.Optional[str] = pydantic.Field(default=None) + """ + Code of the stock exchange the company is listed on. Required for publicly traded companies. + """ + + first_name: str = pydantic.Field() + """ + Business contact first name. + """ + + last_name: str = pydantic.Field() + """ + Business contact last name. + """ + + phone_number: str = pydantic.Field() + """ + Support contact phone number in E.164 format. + """ + + email: str = pydantic.Field() + """ + Support contact email address. + """ + + street_address: str = pydantic.Field() + """ + Street address of the business. + """ + + city: str = pydantic.Field() + """ + City of the business address. + """ + + state_or_province: typing.Optional[str] = pydantic.Field(default=None) + """ + State or province of the business address. + """ + + country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code of the business address. + """ + + zip: str = pydantic.Field() + """ + ZIP or postal code of the business address. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the identity verification process explaining the current `status`. + """ + + mock: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the Brand is a mock brand for testing. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Brand was created, in ISO 8601 format. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Brand was last updated, in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/create_brands_response_entity_type.py b/src/wavix/ten_dlc/brands/types/create_brands_response_entity_type.py new file mode 100644 index 0000000..07824b0 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/create_brands_response_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreateBrandsResponseEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/create_brands_response_status.py b/src/wavix/ten_dlc/brands/types/create_brands_response_status.py new file mode 100644 index 0000000..99faa0b --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/create_brands_response_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CreateBrandsResponseStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/delete_brands_response.py b/src/wavix/ten_dlc/brands/types/delete_brands_response.py new file mode 100644 index 0000000..4d5ccc5 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/delete_brands_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteBrandsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/get_brands_response.py b/src/wavix/ten_dlc/brands/types/get_brands_response.py new file mode 100644 index 0000000..c5c818b --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/get_brands_response.py @@ -0,0 +1,143 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .get_brands_response_entity_type import GetBrandsResponseEntityType +from .get_brands_response_status import GetBrandsResponseStatus + + +class GetBrandsResponse(UniversalBaseModel): + """ + Represents a 10DLC brand registered for application-to-person messaging. A Brand identifies the business behind one or more messaging campaigns. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand assigned by the registry. + """ + + dba_name: str = pydantic.Field() + """ + Doing-business-as name, or the public-facing brand name. + """ + + company_name: str = pydantic.Field() + """ + Registered legal name of the company that owns the Brand. + """ + + entity_type: GetBrandsResponseEntityType = pydantic.Field() + """ + Company entity type. + """ + + vertical: str = pydantic.Field() + """ + Industry vertical the Brand operates in. + """ + + ein_taxid: str = pydantic.Field() + """ + IRS Employer Identification Number (EIN) or other tax ID of the company. + """ + + ein_taxid_country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code where the Tax ID was issued. + """ + + status: GetBrandsResponseStatus = pydantic.Field() + """ + Brand identity verification status. + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Business website URL. + """ + + stock_symbol: typing.Optional[str] = pydantic.Field(default=None) + """ + Stock ticker symbol of the company. Required for publicly traded companies. + """ + + stock_exchange: typing.Optional[str] = pydantic.Field(default=None) + """ + Code of the stock exchange the company is listed on. Required for publicly traded companies. + """ + + first_name: str = pydantic.Field() + """ + Business contact first name. + """ + + last_name: str = pydantic.Field() + """ + Business contact last name. + """ + + phone_number: str = pydantic.Field() + """ + Support contact phone number in E.164 format. + """ + + email: str = pydantic.Field() + """ + Support contact email address. + """ + + street_address: str = pydantic.Field() + """ + Street address of the business. + """ + + city: str = pydantic.Field() + """ + City of the business address. + """ + + state_or_province: typing.Optional[str] = pydantic.Field(default=None) + """ + State or province of the business address. + """ + + country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code of the business address. + """ + + zip: str = pydantic.Field() + """ + ZIP or postal code of the business address. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the identity verification process explaining the current `status`. + """ + + mock: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the Brand is a mock brand for testing. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Brand was created, in ISO 8601 format. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Brand was last updated, in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/get_brands_response_entity_type.py b/src/wavix/ten_dlc/brands/types/get_brands_response_entity_type.py new file mode 100644 index 0000000..8a664a5 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/get_brands_response_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetBrandsResponseEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/get_brands_response_status.py b/src/wavix/ten_dlc/brands/types/get_brands_response_status.py new file mode 100644 index 0000000..7bacc7e --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/get_brands_response_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +GetBrandsResponseStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/list_brands_response.py b/src/wavix/ten_dlc/brands/types/list_brands_response.py new file mode 100644 index 0000000..41a3f2f --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/list_brands_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.ten_dlc_brand import TenDlcBrand +from .list_brands_response_pagination import ListBrandsResponsePagination + + +class ListBrandsResponse(UniversalBaseModel): + """ + A list of 10DLC Brands + """ + + items: typing.List[TenDlcBrand] = pydantic.Field() + """ + A paginated list of 10DLC Brands matching the filter criteria + """ + + pagination: ListBrandsResponsePagination = pydantic.Field() + """ + Pagination details + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/list_brands_response_pagination.py b/src/wavix/ten_dlc/brands/types/list_brands_response_pagination.py new file mode 100644 index 0000000..9f84f5d --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/list_brands_response_pagination.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ListBrandsResponsePagination(UniversalBaseModel): + """ + Pagination details + """ + + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_request_use_case.py b/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_request_use_case.py new file mode 100644 index 0000000..d7da027 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_request_use_case.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +QualifyUsecaseBrandsRequestUseCase = typing.Union[ + typing.Literal[ + "AGENTS_FRANCHISES", + "CARRIER_EXEMPT", + "CHARITY", + "EMERGENCY", + "K12_EDUCATION", + "LOW_VOLUME", + "M2M", + "MIXED", + "POLITICAL", + "PROXY", + "SOCIAL", + "SOLE_PROPRIETOR", + "SWEEPSTAKE", + "TRIAL", + "UCAAS_HIGH", + "UCAAS_LOW", + "2FA", + "ACCOUNT_NOTIFICATION", + "CUSTOMER_CARE", + "DELIVERY_NOTIFICATION", + "FRAUD_ALERT", + "HIGHER_EDUCATION", + "MARKETING", + "POLLING_VOTING", + "PUBLIC_SERVICE_ANNOUNCEMENT", + "SECURITY_ALERT", + ], + typing.Any, +] diff --git a/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_response.py b/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_response.py new file mode 100644 index 0000000..06e8776 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/qualify_usecase_brands_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.ten_dlcmno_metadata import TenDlcmnoMetadata + + +class QualifyUsecaseBrandsResponse(UniversalBaseModel): + mno_metadata: typing.List[TenDlcmnoMetadata] = pydantic.Field() + """ + An array MNO-specific attributes (e.g. AT&T message class) for every MNO the Brand is qualified to run a Campaign with the specified use case. + """ + + monthly_fee: float = pydantic.Field() + """ + Monthly fee associated with any Campaign with this use case + """ + + usecase: str = pydantic.Field() + """ + The use case name + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_entity_type.py b/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_entity_type.py new file mode 100644 index 0000000..ec7bee0 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandUpdateRequestEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_vertical.py b/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_vertical.py new file mode 100644 index 0000000..6e3ee06 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/ten_dlc_brand_update_request_vertical.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandUpdateRequestVertical = typing.Union[ + typing.Literal[ + "HEALTHCARE", + "PROFESSIONAL", + "RETAIL", + "TECHNOLOGY", + "EDUCATION", + "FINANCIAL", + "NON_PROFIT", + "GOVERNMENT", + "OTHER", + ], + typing.Any, +] diff --git a/src/wavix/ten_dlc/brands/types/update_brands_response.py b/src/wavix/ten_dlc/brands/types/update_brands_response.py new file mode 100644 index 0000000..a0406c5 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/update_brands_response.py @@ -0,0 +1,143 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .update_brands_response_entity_type import UpdateBrandsResponseEntityType +from .update_brands_response_status import UpdateBrandsResponseStatus + + +class UpdateBrandsResponse(UniversalBaseModel): + """ + Represents a 10DLC brand registered for application-to-person messaging. A Brand identifies the business behind one or more messaging campaigns. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand assigned by the registry. + """ + + dba_name: str = pydantic.Field() + """ + Doing-business-as name, or the public-facing brand name. + """ + + company_name: str = pydantic.Field() + """ + Registered legal name of the company that owns the Brand. + """ + + entity_type: UpdateBrandsResponseEntityType = pydantic.Field() + """ + Company entity type. + """ + + vertical: str = pydantic.Field() + """ + Industry vertical the Brand operates in. + """ + + ein_taxid: str = pydantic.Field() + """ + IRS Employer Identification Number (EIN) or other tax ID of the company. + """ + + ein_taxid_country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code where the Tax ID was issued. + """ + + status: UpdateBrandsResponseStatus = pydantic.Field() + """ + Brand identity verification status. + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Business website URL. + """ + + stock_symbol: typing.Optional[str] = pydantic.Field(default=None) + """ + Stock ticker symbol of the company. Required for publicly traded companies. + """ + + stock_exchange: typing.Optional[str] = pydantic.Field(default=None) + """ + Code of the stock exchange the company is listed on. Required for publicly traded companies. + """ + + first_name: str = pydantic.Field() + """ + Business contact first name. + """ + + last_name: str = pydantic.Field() + """ + Business contact last name. + """ + + phone_number: str = pydantic.Field() + """ + Support contact phone number in E.164 format. + """ + + email: str = pydantic.Field() + """ + Support contact email address. + """ + + street_address: str = pydantic.Field() + """ + Street address of the business. + """ + + city: str = pydantic.Field() + """ + City of the business address. + """ + + state_or_province: typing.Optional[str] = pydantic.Field(default=None) + """ + State or province of the business address. + """ + + country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code of the business address. + """ + + zip: str = pydantic.Field() + """ + ZIP or postal code of the business address. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the identity verification process explaining the current `status`. + """ + + mock: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the Brand is a mock brand for testing. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Brand was created, in ISO 8601 format. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Brand was last updated, in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/brands/types/update_brands_response_entity_type.py b/src/wavix/ten_dlc/brands/types/update_brands_response_entity_type.py new file mode 100644 index 0000000..6b944d9 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/update_brands_response_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +UpdateBrandsResponseEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/ten_dlc/brands/types/update_brands_response_status.py b/src/wavix/ten_dlc/brands/types/update_brands_response_status.py new file mode 100644 index 0000000..7f490a9 --- /dev/null +++ b/src/wavix/ten_dlc/brands/types/update_brands_response_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +UpdateBrandsResponseStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/ten_dlc/campaign_numbers/__init__.py b/src/wavix/ten_dlc/campaign_numbers/__init__.py new file mode 100644 index 0000000..adb823a --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import LinkCampaignNumbersResponse, ListCampaignNumbersResponse, UnlinkCampaignNumbersResponse +_dynamic_imports: typing.Dict[str, str] = { + "LinkCampaignNumbersResponse": ".types", + "ListCampaignNumbersResponse": ".types", + "UnlinkCampaignNumbersResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["LinkCampaignNumbersResponse", "ListCampaignNumbersResponse", "UnlinkCampaignNumbersResponse"] diff --git a/src/wavix/ten_dlc/campaign_numbers/client.py b/src/wavix/ten_dlc/campaign_numbers/client.py new file mode 100644 index 0000000..ad454ed --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/client.py @@ -0,0 +1,304 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawCampaignNumbersClient, RawCampaignNumbersClient +from .types.link_campaign_numbers_response import LinkCampaignNumbersResponse +from .types.list_campaign_numbers_response import ListCampaignNumbersResponse +from .types.unlink_campaign_numbers_response import UnlinkCampaignNumbersResponse + + +class CampaignNumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCampaignNumbersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCampaignNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCampaignNumbersClient + """ + return self._raw_client + + def link( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> LinkCampaignNumbersResponse: + """ + Links a phone number to a 10DLC Campaign. Wavix automatically creates a Sender ID once the number is approved. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to link to the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LinkCampaignNumbersResponse + Returns a success confirmation. The phone number is linked to the Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaign_numbers.link( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", + ) + """ + _response = self._raw_client.link(brand_id, campaign_id, number, request_options=request_options) + return _response.data + + def unlink( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> UnlinkCampaignNumbersResponse: + """ + Unlinks a phone number from a 10DLC Campaign. The associated Sender ID is also deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to unlink from the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UnlinkCampaignNumbersResponse + Returns a success confirmation. The phone number is unlinked from the Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaign_numbers.unlink( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", + ) + """ + _response = self._raw_client.unlink(brand_id, campaign_id, number, request_options=request_options) + return _response.data + + def list( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ListCampaignNumbersResponse: + """ + Returns the phone numbers linked to the 10DLC Campaign identified by `campaign_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCampaignNumbersResponse + Returns the phone numbers linked to the Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaign_numbers.list( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + ) + """ + _response = self._raw_client.list(brand_id, campaign_id, request_options=request_options) + return _response.data + + +class AsyncCampaignNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCampaignNumbersClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCampaignNumbersClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCampaignNumbersClient + """ + return self._raw_client + + async def link( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> LinkCampaignNumbersResponse: + """ + Links a phone number to a 10DLC Campaign. Wavix automatically creates a Sender ID once the number is approved. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to link to the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + LinkCampaignNumbersResponse + Returns a success confirmation. The phone number is linked to the Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaign_numbers.link( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.link(brand_id, campaign_id, number, request_options=request_options) + return _response.data + + async def unlink( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> UnlinkCampaignNumbersResponse: + """ + Unlinks a phone number from a 10DLC Campaign. The associated Sender ID is also deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to unlink from the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UnlinkCampaignNumbersResponse + Returns a success confirmation. The phone number is unlinked from the Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaign_numbers.unlink( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + number="17029641104", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.unlink(brand_id, campaign_id, number, request_options=request_options) + return _response.data + + async def list( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> ListCampaignNumbersResponse: + """ + Returns the phone numbers linked to the 10DLC Campaign identified by `campaign_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCampaignNumbersResponse + Returns the phone numbers linked to the Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaign_numbers.list( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(brand_id, campaign_id, request_options=request_options) + return _response.data diff --git a/src/wavix/ten_dlc/campaign_numbers/raw_client.py b/src/wavix/ten_dlc/campaign_numbers/raw_client.py new file mode 100644 index 0000000..e43c73d --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/raw_client.py @@ -0,0 +1,448 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from .types.link_campaign_numbers_response import LinkCampaignNumbersResponse +from .types.list_campaign_numbers_response import ListCampaignNumbersResponse +from .types.unlink_campaign_numbers_response import UnlinkCampaignNumbersResponse +from pydantic import ValidationError + + +class RawCampaignNumbersClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def link( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[LinkCampaignNumbersResponse]: + """ + Links a phone number to a 10DLC Campaign. Wavix automatically creates a Sender ID once the number is approved. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to link to the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[LinkCampaignNumbersResponse] + Returns a success confirmation. The phone number is linked to the Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers/{encode_path_param(number)}", + method="POST", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + LinkCampaignNumbersResponse, + parse_obj_as( + type_=LinkCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def unlink( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[UnlinkCampaignNumbersResponse]: + """ + Unlinks a phone number from a 10DLC Campaign. The associated Sender ID is also deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to unlink from the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UnlinkCampaignNumbersResponse] + Returns a success confirmation. The phone number is unlinked from the Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers/{encode_path_param(number)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UnlinkCampaignNumbersResponse, + parse_obj_as( + type_=UnlinkCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[ListCampaignNumbersResponse]: + """ + Returns the phone numbers linked to the 10DLC Campaign identified by `campaign_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListCampaignNumbersResponse] + Returns the phone numbers linked to the Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCampaignNumbersResponse, + parse_obj_as( + type_=ListCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCampaignNumbersClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def link( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[LinkCampaignNumbersResponse]: + """ + Links a phone number to a 10DLC Campaign. Wavix automatically creates a Sender ID once the number is approved. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to link to the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[LinkCampaignNumbersResponse] + Returns a success confirmation. The phone number is linked to the Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers/{encode_path_param(number)}", + method="POST", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + LinkCampaignNumbersResponse, + parse_obj_as( + type_=LinkCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def unlink( + self, brand_id: str, campaign_id: str, number: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[UnlinkCampaignNumbersResponse]: + """ + Unlinks a phone number from a 10DLC Campaign. The associated Sender ID is also deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + number : str + The phone number to unlink from the Campaign, in E.164 format. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UnlinkCampaignNumbersResponse] + Returns a success confirmation. The phone number is unlinked from the Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers/{encode_path_param(number)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UnlinkCampaignNumbersResponse, + parse_obj_as( + type_=UnlinkCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[ListCampaignNumbersResponse]: + """ + Returns the phone numbers linked to the 10DLC Campaign identified by `campaign_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListCampaignNumbersResponse] + Returns the phone numbers linked to the Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/numbers", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCampaignNumbersResponse, + parse_obj_as( + type_=ListCampaignNumbersResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/campaign_numbers/types/__init__.py b/src/wavix/ten_dlc/campaign_numbers/types/__init__.py new file mode 100644 index 0000000..f9e5326 --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/types/__init__.py @@ -0,0 +1,40 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .link_campaign_numbers_response import LinkCampaignNumbersResponse + from .list_campaign_numbers_response import ListCampaignNumbersResponse + from .unlink_campaign_numbers_response import UnlinkCampaignNumbersResponse +_dynamic_imports: typing.Dict[str, str] = { + "LinkCampaignNumbersResponse": ".link_campaign_numbers_response", + "ListCampaignNumbersResponse": ".list_campaign_numbers_response", + "UnlinkCampaignNumbersResponse": ".unlink_campaign_numbers_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["LinkCampaignNumbersResponse", "ListCampaignNumbersResponse", "UnlinkCampaignNumbersResponse"] diff --git a/src/wavix/ten_dlc/campaign_numbers/types/link_campaign_numbers_response.py b/src/wavix/ten_dlc/campaign_numbers/types/link_campaign_numbers_response.py new file mode 100644 index 0000000..688e653 --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/types/link_campaign_numbers_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class LinkCampaignNumbersResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaign_numbers/types/list_campaign_numbers_response.py b/src/wavix/ten_dlc/campaign_numbers/types/list_campaign_numbers_response.py new file mode 100644 index 0000000..801b4d6 --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/types/list_campaign_numbers_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.ten_dlc_campaign_number import TenDlcCampaignNumber + + +class ListCampaignNumbersResponse(UniversalBaseModel): + brand_id: str = pydantic.Field() + """ + Unique identifier of the 10DLC Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the 10DLC Campaign the numbers belong to. + """ + + numbers: typing.List[TenDlcCampaignNumber] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, with their provisioning status. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaign_numbers/types/unlink_campaign_numbers_response.py b/src/wavix/ten_dlc/campaign_numbers/types/unlink_campaign_numbers_response.py new file mode 100644 index 0000000..c3acc23 --- /dev/null +++ b/src/wavix/ten_dlc/campaign_numbers/types/unlink_campaign_numbers_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UnlinkCampaignNumbersResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/__init__.py b/src/wavix/ten_dlc/campaigns/__init__.py new file mode 100644 index 0000000..45daec6 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/__init__.py @@ -0,0 +1,64 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateCampaignsResponse, + DeleteCampaignsResponse, + GetCampaignsResponse, + ListByBrandCampaignsResponse, + ListByBrandCampaignsResponsePagination, + ListCampaignsResponse, + ListCampaignsResponsePagination, + TenDlcCampaignUpdateRequestUsecase, + UpdateCampaignsResponse, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateCampaignsResponse": ".types", + "DeleteCampaignsResponse": ".types", + "GetCampaignsResponse": ".types", + "ListByBrandCampaignsResponse": ".types", + "ListByBrandCampaignsResponsePagination": ".types", + "ListCampaignsResponse": ".types", + "ListCampaignsResponsePagination": ".types", + "TenDlcCampaignUpdateRequestUsecase": ".types", + "UpdateCampaignsResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateCampaignsResponse", + "DeleteCampaignsResponse", + "GetCampaignsResponse", + "ListByBrandCampaignsResponse", + "ListByBrandCampaignsResponsePagination", + "ListCampaignsResponse", + "ListCampaignsResponsePagination", + "TenDlcCampaignUpdateRequestUsecase", + "UpdateCampaignsResponse", +] diff --git a/src/wavix/ten_dlc/campaigns/client.py b/src/wavix/ten_dlc/campaigns/client.py new file mode 100644 index 0000000..bd3e159 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/client.py @@ -0,0 +1,1493 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.success_response import SuccessResponse +from .raw_client import AsyncRawCampaignsClient, RawCampaignsClient +from .types.create_campaigns_response import CreateCampaignsResponse +from .types.delete_campaigns_response import DeleteCampaignsResponse +from .types.get_campaigns_response import GetCampaignsResponse +from .types.list_by_brand_campaigns_response import ListByBrandCampaignsResponse +from .types.list_campaigns_response import ListCampaignsResponse +from .types.ten_dlc_campaign_update_request_usecase import TenDlcCampaignUpdateRequestUsecase +from .types.update_campaigns_response import UpdateCampaignsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class CampaignsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawCampaignsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawCampaignsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawCampaignsClient + """ + return self._raw_client + + def list( + self, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCampaignsResponse: + """ + Returns a paginated list of 10DLC Campaigns for the authenticated account, filtered by date, status, and use case. + + Parameters + ---------- + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCampaignsResponse + Returns a paginated list of 10DLC Campaigns. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.list( + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat( + "2024-08-22", + ), + created_after=datetime.date.fromisoformat( + "2024-08-22", + ), + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list( + name=name, + usecase=usecase, + status=status, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def list_by_brand( + self, + brand_id: str, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListByBrandCampaignsResponse: + """ + Returns a paginated list of 10DLC Campaigns associated with the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListByBrandCampaignsResponse + Returns a paginated list of 10DLC Campaigns. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.list_by_brand( + brand_id="BM20QP9", + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat( + "2024-08-22", + ), + created_after=datetime.date.fromisoformat( + "2024-08-22", + ), + page=1, + per_page=25, + ) + """ + _response = self._raw_client.list_by_brand( + brand_id, + name=name, + usecase=usecase, + status=status, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + def create( + self, + brand_id: str, + *, + affiliate_marketing: bool, + age_gated: bool, + auto_renewal: bool, + direct_lending: bool, + embedded_links: bool, + description: str, + optin_workflow: str, + help: bool, + help_keywords: str, + help_message: str, + optin: bool, + optin_keywords: str, + optin_message: str, + optout: bool, + optout_keywords: str, + optout_message: str, + name: str, + sample1: str, + mock: bool, + usecase: str, + terms_conditions: str, + embedded_phones: typing.Optional[bool] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateCampaignsResponse: + """ + Registers a 10DLC Campaign under the 10DLC Brand identified by `brand_id`. The Brand must have a verified identity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + affiliate_marketing : bool + Indicates whether the Campaign is used for affiliate marketing. + + age_gated : bool + Indicates whether the Campaign messages contain age-gated content. + + auto_renewal : bool + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + direct_lending : bool + Indicates whether the Campaign messages contain direct lending content. + + embedded_links : bool + Indicates whether the Campaign messages contain embedded links. + + description : str + Description of the Campaign and its messaging purpose. + + optin_workflow : str + Description of the workflow through which subscribers opt in to the Campaign. + + help : bool + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword such as HELP or INFO. + + help_keywords : str + Comma-separated list of help keywords. Keywords are case-insensitive. + + help_message : str + Acknowledgement sent when a subscriber texts a help keyword. + + optin : bool + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optin_keywords : str + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + optin_message : str + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout : bool + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword such as STOP or QUIT. + + optout_keywords : str + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + optout_message : str + Acknowledgement sent when a subscriber texts an opt-out keyword. + + name : str + Display name of the Campaign. + + sample1 : str + Sample message demonstrating the content sent through the Campaign. + + mock : bool + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + + usecase : str + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + + terms_conditions : str + URL of the Campaign terms and conditions. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateCampaignsResponse + Returns the registered 10DLC Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.create( + brand_id="BM20QP9", + affiliate_marketing=False, + age_gated=False, + auto_renewal=False, + direct_lending=False, + embedded_links=False, + embedded_phones=False, + embedded_link_sample="https://site.com/verify", + description="Our campaign aims to …", + optin_workflow="Our SMS ...", + help=True, + help_keywords="help", + help_message="For help, please visit www.site.com. To opt-out, reply STOP.", + optin=True, + optin_keywords="begin,start", + optin_message="You are now opted-in for help please reply HELP, to stop please reply STOP", + optout=True, + optout_keywords="stop,quit,unsubscribe", + optout_message="You are now opted out and will receive no further messages", + name="My first campaign", + sample1="Your verification code is XXXXXX", + sample2="XXXX is your verification code", + sample3="Your code is XXXXXX, valid for 10 minutes", + sample4="Use code XXXXXX to confirm your login", + sample5="XXXXXX is your one-time passcode", + mock=False, + usecase="2FA", + privacy_policy="https://site.com/privacy-policy", + terms_conditions="https://site.com/terms-and-conditions", + ) + """ + _response = self._raw_client.create( + brand_id, + affiliate_marketing=affiliate_marketing, + age_gated=age_gated, + auto_renewal=auto_renewal, + direct_lending=direct_lending, + embedded_links=embedded_links, + description=description, + optin_workflow=optin_workflow, + help=help, + help_keywords=help_keywords, + help_message=help_message, + optin=optin, + optin_keywords=optin_keywords, + optin_message=optin_message, + optout=optout, + optout_keywords=optout_keywords, + optout_message=optout_message, + name=name, + sample1=sample1, + mock=mock, + usecase=usecase, + terms_conditions=terms_conditions, + embedded_phones=embedded_phones, + embedded_link_sample=embedded_link_sample, + sample2=sample2, + sample3=sample3, + sample4=sample4, + sample5=sample5, + privacy_policy=privacy_policy, + request_options=request_options, + ) + return _response.data + + def get( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetCampaignsResponse: + """ + Returns the 10DLC Campaign identified by `campaign_id` under the Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCampaignsResponse + Returns the 10DLC Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.get( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + """ + _response = self._raw_client.get(brand_id, campaign_id, request_options=request_options) + return _response.data + + def update( + self, + brand_id: str, + campaign_id: str, + *, + name: typing.Optional[str] = OMIT, + usecase: typing.Optional[TenDlcCampaignUpdateRequestUsecase] = OMIT, + description: typing.Optional[str] = OMIT, + embedded_links: typing.Optional[bool] = OMIT, + embedded_phones: typing.Optional[bool] = OMIT, + age_gated: typing.Optional[bool] = OMIT, + direct_lending: typing.Optional[bool] = OMIT, + optin: typing.Optional[bool] = OMIT, + optout: typing.Optional[bool] = OMIT, + help: typing.Optional[bool] = OMIT, + sample1: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + optin_workflow: typing.Optional[str] = OMIT, + help_message: typing.Optional[str] = OMIT, + optin_message: typing.Optional[str] = OMIT, + optout_message: typing.Optional[str] = OMIT, + auto_renewal: typing.Optional[bool] = OMIT, + optin_keywords: typing.Optional[str] = OMIT, + help_keywords: typing.Optional[str] = OMIT, + optout_keywords: typing.Optional[str] = OMIT, + terms_conditions: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> UpdateCampaignsResponse: + """ + Updates the 10DLC Campaign identified by `campaign_id`. Only the provided fields are changed. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + name : typing.Optional[str] + Display name of the Campaign. + + usecase : typing.Optional[TenDlcCampaignUpdateRequestUsecase] + Registered use case for the Campaign. One of `CUSTOMER_CARE` (customer support messaging), `MARKETING` (promotional content), `ACCOUNT_NOTIFICATION` (account-related alerts), `FRAUD_ALERT` (fraud and suspicious-activity warnings), `PUBLIC_SERVICE_ANNOUNCEMENT` (public-interest notices), or `SECURITY_ALERT` (security-related warnings). + + description : typing.Optional[str] + Description of the Campaign and its messaging purpose. + + embedded_links : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded links. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + age_gated : typing.Optional[bool] + Indicates whether the Campaign messages contain age-gated content. + + direct_lending : typing.Optional[bool] + Indicates whether the Campaign messages contain direct lending content. + + optin : typing.Optional[bool] + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optout : typing.Optional[bool] + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword. + + help : typing.Optional[bool] + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword. + + sample1 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + optin_workflow : typing.Optional[str] + Description of the workflow through which subscribers opt in to the Campaign. + + help_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts a help keyword. + + optin_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-out keyword. + + auto_renewal : typing.Optional[bool] + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + optin_keywords : typing.Optional[str] + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + help_keywords : typing.Optional[str] + Comma-separated list of help keywords. Keywords are case-insensitive. + + optout_keywords : typing.Optional[str] + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + terms_conditions : typing.Optional[str] + URL of the Campaign terms and conditions. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UpdateCampaignsResponse + Returns the updated 10DLC Campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.update( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + """ + _response = self._raw_client.update( + brand_id, + campaign_id, + name=name, + usecase=usecase, + description=description, + embedded_links=embedded_links, + embedded_phones=embedded_phones, + age_gated=age_gated, + direct_lending=direct_lending, + optin=optin, + optout=optout, + help=help, + sample1=sample1, + sample2=sample2, + sample3=sample3, + sample4=sample4, + sample5=sample5, + optin_workflow=optin_workflow, + help_message=help_message, + optin_message=optin_message, + optout_message=optout_message, + auto_renewal=auto_renewal, + optin_keywords=optin_keywords, + help_keywords=help_keywords, + optout_keywords=optout_keywords, + terms_conditions=terms_conditions, + privacy_policy=privacy_policy, + embedded_link_sample=embedded_link_sample, + request_options=request_options, + ) + return _response.data + + def delete( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteCampaignsResponse: + """ + Deletes a 10DLC Campaign. Associated phone numbers cannot be used as Sender IDs once the Campaign is deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteCampaignsResponse + Returns a success confirmation. The 10DLC Campaign is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.delete( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + """ + _response = self._raw_client.delete(brand_id, campaign_id, request_options=request_options) + return _response.data + + def nudge( + self, + brand_id: str, + campaign_id: str, + *, + nudge_intent: str, + description: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Requests action on a pending or rejected 10DLC Campaign. Use `nudge_intent` to specify the action: + - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + Note: + - The Campaign must be at least 72 hours old. + - Only one nudge request per Campaign is allowed every 24 hours. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + nudge_intent : str + Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. + Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + + description : str + Description of the nudge request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The nudge request is submitted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.campaigns.nudge( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + nudge_intent="REVIEW", + description="Please review the campaign.", + ) + """ + _response = self._raw_client.nudge( + brand_id, campaign_id, nudge_intent=nudge_intent, description=description, request_options=request_options + ) + return _response.data + + +class AsyncCampaignsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawCampaignsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawCampaignsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawCampaignsClient + """ + return self._raw_client + + async def list( + self, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListCampaignsResponse: + """ + Returns a paginated list of 10DLC Campaigns for the authenticated account, filtered by date, status, and use case. + + Parameters + ---------- + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListCampaignsResponse + Returns a paginated list of 10DLC Campaigns. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.list( + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat( + "2024-08-22", + ), + created_after=datetime.date.fromisoformat( + "2024-08-22", + ), + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list( + name=name, + usecase=usecase, + status=status, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def list_by_brand( + self, + brand_id: str, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> ListByBrandCampaignsResponse: + """ + Returns a paginated list of 10DLC Campaigns associated with the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ListByBrandCampaignsResponse + Returns a paginated list of 10DLC Campaigns. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.list_by_brand( + brand_id="BM20QP9", + name="Name", + usecase="2FA", + status="APPROVED", + mock=True, + created_before=datetime.date.fromisoformat( + "2024-08-22", + ), + created_after=datetime.date.fromisoformat( + "2024-08-22", + ), + page=1, + per_page=25, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_by_brand( + brand_id, + name=name, + usecase=usecase, + status=status, + mock=mock, + created_before=created_before, + created_after=created_after, + page=page, + per_page=per_page, + request_options=request_options, + ) + return _response.data + + async def create( + self, + brand_id: str, + *, + affiliate_marketing: bool, + age_gated: bool, + auto_renewal: bool, + direct_lending: bool, + embedded_links: bool, + description: str, + optin_workflow: str, + help: bool, + help_keywords: str, + help_message: str, + optin: bool, + optin_keywords: str, + optin_message: str, + optout: bool, + optout_keywords: str, + optout_message: str, + name: str, + sample1: str, + mock: bool, + usecase: str, + terms_conditions: str, + embedded_phones: typing.Optional[bool] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> CreateCampaignsResponse: + """ + Registers a 10DLC Campaign under the 10DLC Brand identified by `brand_id`. The Brand must have a verified identity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + affiliate_marketing : bool + Indicates whether the Campaign is used for affiliate marketing. + + age_gated : bool + Indicates whether the Campaign messages contain age-gated content. + + auto_renewal : bool + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + direct_lending : bool + Indicates whether the Campaign messages contain direct lending content. + + embedded_links : bool + Indicates whether the Campaign messages contain embedded links. + + description : str + Description of the Campaign and its messaging purpose. + + optin_workflow : str + Description of the workflow through which subscribers opt in to the Campaign. + + help : bool + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword such as HELP or INFO. + + help_keywords : str + Comma-separated list of help keywords. Keywords are case-insensitive. + + help_message : str + Acknowledgement sent when a subscriber texts a help keyword. + + optin : bool + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optin_keywords : str + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + optin_message : str + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout : bool + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword such as STOP or QUIT. + + optout_keywords : str + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + optout_message : str + Acknowledgement sent when a subscriber texts an opt-out keyword. + + name : str + Display name of the Campaign. + + sample1 : str + Sample message demonstrating the content sent through the Campaign. + + mock : bool + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + + usecase : str + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + + terms_conditions : str + URL of the Campaign terms and conditions. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateCampaignsResponse + Returns the registered 10DLC Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.create( + brand_id="BM20QP9", + affiliate_marketing=False, + age_gated=False, + auto_renewal=False, + direct_lending=False, + embedded_links=False, + embedded_phones=False, + embedded_link_sample="https://site.com/verify", + description="Our campaign aims to …", + optin_workflow="Our SMS ...", + help=True, + help_keywords="help", + help_message="For help, please visit www.site.com. To opt-out, reply STOP.", + optin=True, + optin_keywords="begin,start", + optin_message="You are now opted-in for help please reply HELP, to stop please reply STOP", + optout=True, + optout_keywords="stop,quit,unsubscribe", + optout_message="You are now opted out and will receive no further messages", + name="My first campaign", + sample1="Your verification code is XXXXXX", + sample2="XXXX is your verification code", + sample3="Your code is XXXXXX, valid for 10 minutes", + sample4="Use code XXXXXX to confirm your login", + sample5="XXXXXX is your one-time passcode", + mock=False, + usecase="2FA", + privacy_policy="https://site.com/privacy-policy", + terms_conditions="https://site.com/terms-and-conditions", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + brand_id, + affiliate_marketing=affiliate_marketing, + age_gated=age_gated, + auto_renewal=auto_renewal, + direct_lending=direct_lending, + embedded_links=embedded_links, + description=description, + optin_workflow=optin_workflow, + help=help, + help_keywords=help_keywords, + help_message=help_message, + optin=optin, + optin_keywords=optin_keywords, + optin_message=optin_message, + optout=optout, + optout_keywords=optout_keywords, + optout_message=optout_message, + name=name, + sample1=sample1, + mock=mock, + usecase=usecase, + terms_conditions=terms_conditions, + embedded_phones=embedded_phones, + embedded_link_sample=embedded_link_sample, + sample2=sample2, + sample3=sample3, + sample4=sample4, + sample5=sample5, + privacy_policy=privacy_policy, + request_options=request_options, + ) + return _response.data + + async def get( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> GetCampaignsResponse: + """ + Returns the 10DLC Campaign identified by `campaign_id` under the Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GetCampaignsResponse + Returns the 10DLC Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.get( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(brand_id, campaign_id, request_options=request_options) + return _response.data + + async def update( + self, + brand_id: str, + campaign_id: str, + *, + name: typing.Optional[str] = OMIT, + usecase: typing.Optional[TenDlcCampaignUpdateRequestUsecase] = OMIT, + description: typing.Optional[str] = OMIT, + embedded_links: typing.Optional[bool] = OMIT, + embedded_phones: typing.Optional[bool] = OMIT, + age_gated: typing.Optional[bool] = OMIT, + direct_lending: typing.Optional[bool] = OMIT, + optin: typing.Optional[bool] = OMIT, + optout: typing.Optional[bool] = OMIT, + help: typing.Optional[bool] = OMIT, + sample1: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + optin_workflow: typing.Optional[str] = OMIT, + help_message: typing.Optional[str] = OMIT, + optin_message: typing.Optional[str] = OMIT, + optout_message: typing.Optional[str] = OMIT, + auto_renewal: typing.Optional[bool] = OMIT, + optin_keywords: typing.Optional[str] = OMIT, + help_keywords: typing.Optional[str] = OMIT, + optout_keywords: typing.Optional[str] = OMIT, + terms_conditions: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> UpdateCampaignsResponse: + """ + Updates the 10DLC Campaign identified by `campaign_id`. Only the provided fields are changed. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + name : typing.Optional[str] + Display name of the Campaign. + + usecase : typing.Optional[TenDlcCampaignUpdateRequestUsecase] + Registered use case for the Campaign. One of `CUSTOMER_CARE` (customer support messaging), `MARKETING` (promotional content), `ACCOUNT_NOTIFICATION` (account-related alerts), `FRAUD_ALERT` (fraud and suspicious-activity warnings), `PUBLIC_SERVICE_ANNOUNCEMENT` (public-interest notices), or `SECURITY_ALERT` (security-related warnings). + + description : typing.Optional[str] + Description of the Campaign and its messaging purpose. + + embedded_links : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded links. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + age_gated : typing.Optional[bool] + Indicates whether the Campaign messages contain age-gated content. + + direct_lending : typing.Optional[bool] + Indicates whether the Campaign messages contain direct lending content. + + optin : typing.Optional[bool] + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optout : typing.Optional[bool] + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword. + + help : typing.Optional[bool] + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword. + + sample1 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + optin_workflow : typing.Optional[str] + Description of the workflow through which subscribers opt in to the Campaign. + + help_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts a help keyword. + + optin_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-out keyword. + + auto_renewal : typing.Optional[bool] + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + optin_keywords : typing.Optional[str] + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + help_keywords : typing.Optional[str] + Comma-separated list of help keywords. Keywords are case-insensitive. + + optout_keywords : typing.Optional[str] + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + terms_conditions : typing.Optional[str] + URL of the Campaign terms and conditions. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + UpdateCampaignsResponse + Returns the updated 10DLC Campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.update( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update( + brand_id, + campaign_id, + name=name, + usecase=usecase, + description=description, + embedded_links=embedded_links, + embedded_phones=embedded_phones, + age_gated=age_gated, + direct_lending=direct_lending, + optin=optin, + optout=optout, + help=help, + sample1=sample1, + sample2=sample2, + sample3=sample3, + sample4=sample4, + sample5=sample5, + optin_workflow=optin_workflow, + help_message=help_message, + optin_message=optin_message, + optout_message=optout_message, + auto_renewal=auto_renewal, + optin_keywords=optin_keywords, + help_keywords=help_keywords, + optout_keywords=optout_keywords, + terms_conditions=terms_conditions, + privacy_policy=privacy_policy, + embedded_link_sample=embedded_link_sample, + request_options=request_options, + ) + return _response.data + + async def delete( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteCampaignsResponse: + """ + Deletes a 10DLC Campaign. Associated phone numbers cannot be used as Sender IDs once the Campaign is deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteCampaignsResponse + Returns a success confirmation. The 10DLC Campaign is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.delete( + brand_id="BM20QP9", + campaign_id="CKLCK95", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(brand_id, campaign_id, request_options=request_options) + return _response.data + + async def nudge( + self, + brand_id: str, + campaign_id: str, + *, + nudge_intent: str, + description: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> SuccessResponse: + """ + Requests action on a pending or rejected 10DLC Campaign. Use `nudge_intent` to specify the action: + - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + Note: + - The Campaign must be at least 72 hours old. + - Only one nudge request per Campaign is allowed every 24 hours. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + nudge_intent : str + Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. + Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + + description : str + Description of the nudge request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The nudge request is submitted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.campaigns.nudge( + brand_id="B9FXYNH", + campaign_id="CSJ4TV0", + nudge_intent="REVIEW", + description="Please review the campaign.", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.nudge( + brand_id, campaign_id, nudge_intent=nudge_intent, description=description, request_options=request_options + ) + return _response.data diff --git a/src/wavix/ten_dlc/campaigns/raw_client.py b/src/wavix/ten_dlc/campaigns/raw_client.py new file mode 100644 index 0000000..d422de6 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/raw_client.py @@ -0,0 +1,1840 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.too_many_requests_error import TooManyRequestsError +from ...types.success_response import SuccessResponse +from ...types.validation_error_response import ValidationErrorResponse +from .types.create_campaigns_response import CreateCampaignsResponse +from .types.delete_campaigns_response import DeleteCampaignsResponse +from .types.get_campaigns_response import GetCampaignsResponse +from .types.list_by_brand_campaigns_response import ListByBrandCampaignsResponse +from .types.list_campaigns_response import ListCampaignsResponse +from .types.ten_dlc_campaign_update_request_usecase import TenDlcCampaignUpdateRequestUsecase +from .types.update_campaigns_response import UpdateCampaignsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawCampaignsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListCampaignsResponse]: + """ + Returns a paginated list of 10DLC Campaigns for the authenticated account, filtered by date, status, and use case. + + Parameters + ---------- + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListCampaignsResponse] + Returns a paginated list of 10DLC Campaigns. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/brands/campaigns", + method="GET", + params={ + "name": name, + "usecase": usecase, + "status": status, + "mock": mock, + "created_before": str(created_before) if created_before is not None else None, + "created_after": str(created_after) if created_after is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCampaignsResponse, + parse_obj_as( + type_=ListCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def list_by_brand( + self, + brand_id: str, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ListByBrandCampaignsResponse]: + """ + Returns a paginated list of 10DLC Campaigns associated with the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ListByBrandCampaignsResponse] + Returns a paginated list of 10DLC Campaigns. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns", + method="GET", + params={ + "name": name, + "usecase": usecase, + "status": status, + "mock": mock, + "created_before": str(created_before) if created_before is not None else None, + "created_after": str(created_after) if created_after is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListByBrandCampaignsResponse, + parse_obj_as( + type_=ListByBrandCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + brand_id: str, + *, + affiliate_marketing: bool, + age_gated: bool, + auto_renewal: bool, + direct_lending: bool, + embedded_links: bool, + description: str, + optin_workflow: str, + help: bool, + help_keywords: str, + help_message: str, + optin: bool, + optin_keywords: str, + optin_message: str, + optout: bool, + optout_keywords: str, + optout_message: str, + name: str, + sample1: str, + mock: bool, + usecase: str, + terms_conditions: str, + embedded_phones: typing.Optional[bool] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[CreateCampaignsResponse]: + """ + Registers a 10DLC Campaign under the 10DLC Brand identified by `brand_id`. The Brand must have a verified identity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + affiliate_marketing : bool + Indicates whether the Campaign is used for affiliate marketing. + + age_gated : bool + Indicates whether the Campaign messages contain age-gated content. + + auto_renewal : bool + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + direct_lending : bool + Indicates whether the Campaign messages contain direct lending content. + + embedded_links : bool + Indicates whether the Campaign messages contain embedded links. + + description : str + Description of the Campaign and its messaging purpose. + + optin_workflow : str + Description of the workflow through which subscribers opt in to the Campaign. + + help : bool + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword such as HELP or INFO. + + help_keywords : str + Comma-separated list of help keywords. Keywords are case-insensitive. + + help_message : str + Acknowledgement sent when a subscriber texts a help keyword. + + optin : bool + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optin_keywords : str + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + optin_message : str + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout : bool + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword such as STOP or QUIT. + + optout_keywords : str + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + optout_message : str + Acknowledgement sent when a subscriber texts an opt-out keyword. + + name : str + Display name of the Campaign. + + sample1 : str + Sample message demonstrating the content sent through the Campaign. + + mock : bool + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + + usecase : str + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + + terms_conditions : str + URL of the Campaign terms and conditions. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateCampaignsResponse] + Returns the registered 10DLC Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns", + method="POST", + json={ + "affiliate_marketing": affiliate_marketing, + "age_gated": age_gated, + "auto_renewal": auto_renewal, + "direct_lending": direct_lending, + "embedded_links": embedded_links, + "embedded_phones": embedded_phones, + "embedded_link_sample": embedded_link_sample, + "description": description, + "optin_workflow": optin_workflow, + "help": help, + "help_keywords": help_keywords, + "help_message": help_message, + "optin": optin, + "optin_keywords": optin_keywords, + "optin_message": optin_message, + "optout": optout, + "optout_keywords": optout_keywords, + "optout_message": optout_message, + "name": name, + "sample1": sample1, + "sample2": sample2, + "sample3": sample3, + "sample4": sample4, + "sample5": sample5, + "mock": mock, + "usecase": usecase, + "privacy_policy": privacy_policy, + "terms_conditions": terms_conditions, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateCampaignsResponse, + parse_obj_as( + type_=CreateCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[GetCampaignsResponse]: + """ + Returns the 10DLC Campaign identified by `campaign_id` under the Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GetCampaignsResponse] + Returns the 10DLC Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCampaignsResponse, + parse_obj_as( + type_=GetCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, + brand_id: str, + campaign_id: str, + *, + name: typing.Optional[str] = OMIT, + usecase: typing.Optional[TenDlcCampaignUpdateRequestUsecase] = OMIT, + description: typing.Optional[str] = OMIT, + embedded_links: typing.Optional[bool] = OMIT, + embedded_phones: typing.Optional[bool] = OMIT, + age_gated: typing.Optional[bool] = OMIT, + direct_lending: typing.Optional[bool] = OMIT, + optin: typing.Optional[bool] = OMIT, + optout: typing.Optional[bool] = OMIT, + help: typing.Optional[bool] = OMIT, + sample1: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + optin_workflow: typing.Optional[str] = OMIT, + help_message: typing.Optional[str] = OMIT, + optin_message: typing.Optional[str] = OMIT, + optout_message: typing.Optional[str] = OMIT, + auto_renewal: typing.Optional[bool] = OMIT, + optin_keywords: typing.Optional[str] = OMIT, + help_keywords: typing.Optional[str] = OMIT, + optout_keywords: typing.Optional[str] = OMIT, + terms_conditions: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[UpdateCampaignsResponse]: + """ + Updates the 10DLC Campaign identified by `campaign_id`. Only the provided fields are changed. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + name : typing.Optional[str] + Display name of the Campaign. + + usecase : typing.Optional[TenDlcCampaignUpdateRequestUsecase] + Registered use case for the Campaign. One of `CUSTOMER_CARE` (customer support messaging), `MARKETING` (promotional content), `ACCOUNT_NOTIFICATION` (account-related alerts), `FRAUD_ALERT` (fraud and suspicious-activity warnings), `PUBLIC_SERVICE_ANNOUNCEMENT` (public-interest notices), or `SECURITY_ALERT` (security-related warnings). + + description : typing.Optional[str] + Description of the Campaign and its messaging purpose. + + embedded_links : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded links. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + age_gated : typing.Optional[bool] + Indicates whether the Campaign messages contain age-gated content. + + direct_lending : typing.Optional[bool] + Indicates whether the Campaign messages contain direct lending content. + + optin : typing.Optional[bool] + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optout : typing.Optional[bool] + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword. + + help : typing.Optional[bool] + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword. + + sample1 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + optin_workflow : typing.Optional[str] + Description of the workflow through which subscribers opt in to the Campaign. + + help_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts a help keyword. + + optin_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-out keyword. + + auto_renewal : typing.Optional[bool] + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + optin_keywords : typing.Optional[str] + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + help_keywords : typing.Optional[str] + Comma-separated list of help keywords. Keywords are case-insensitive. + + optout_keywords : typing.Optional[str] + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + terms_conditions : typing.Optional[str] + URL of the Campaign terms and conditions. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[UpdateCampaignsResponse] + Returns the updated 10DLC Campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="PUT", + json={ + "name": name, + "usecase": usecase, + "description": description, + "embedded_links": embedded_links, + "embedded_phones": embedded_phones, + "age_gated": age_gated, + "direct_lending": direct_lending, + "optin": optin, + "optout": optout, + "help": help, + "sample1": sample1, + "sample2": sample2, + "sample3": sample3, + "sample4": sample4, + "sample5": sample5, + "optin_workflow": optin_workflow, + "help_message": help_message, + "optin_message": optin_message, + "optout_message": optout_message, + "auto_renewal": auto_renewal, + "optin_keywords": optin_keywords, + "help_keywords": help_keywords, + "optout_keywords": optout_keywords, + "terms_conditions": terms_conditions, + "privacy_policy": privacy_policy, + "embedded_link_sample": embedded_link_sample, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UpdateCampaignsResponse, + parse_obj_as( + type_=UpdateCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteCampaignsResponse]: + """ + Deletes a 10DLC Campaign. Associated phone numbers cannot be used as Sender IDs once the Campaign is deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteCampaignsResponse] + Returns a success confirmation. The 10DLC Campaign is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteCampaignsResponse, + parse_obj_as( + type_=DeleteCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def nudge( + self, + brand_id: str, + campaign_id: str, + *, + nudge_intent: str, + description: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[SuccessResponse]: + """ + Requests action on a pending or rejected 10DLC Campaign. Use `nudge_intent` to specify the action: + - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + Note: + - The Campaign must be at least 72 hours old. + - Only one nudge request per Campaign is allowed every 24 hours. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + nudge_intent : str + Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. + Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + + description : str + Description of the nudge request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The nudge request is submitted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/nudge", + method="POST", + json={ + "nudge_intent": nudge_intent, + "description": description, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawCampaignsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListCampaignsResponse]: + """ + Returns a paginated list of 10DLC Campaigns for the authenticated account, filtered by date, status, and use case. + + Parameters + ---------- + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListCampaignsResponse] + Returns a paginated list of 10DLC Campaigns. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/brands/campaigns", + method="GET", + params={ + "name": name, + "usecase": usecase, + "status": status, + "mock": mock, + "created_before": str(created_before) if created_before is not None else None, + "created_after": str(created_after) if created_after is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListCampaignsResponse, + parse_obj_as( + type_=ListCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def list_by_brand( + self, + brand_id: str, + *, + name: typing.Optional[str] = None, + usecase: typing.Optional[str] = None, + status: typing.Optional[str] = None, + mock: typing.Optional[bool] = None, + created_before: typing.Optional[dt.date] = None, + created_after: typing.Optional[dt.date] = None, + page: typing.Optional[int] = None, + per_page: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ListByBrandCampaignsResponse]: + """ + Returns a paginated list of 10DLC Campaigns associated with the 10DLC Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + name : typing.Optional[str] + Filters Campaigns by name. Matches partial values. + + usecase : typing.Optional[str] + Filters Campaigns by use case. + + status : typing.Optional[str] + Filters Campaigns by status. + + mock : typing.Optional[bool] + When `true`, returns only mock Campaigns used for testing. Default `false`. + + created_before : typing.Optional[dt.date] + Returns Campaigns created on or before this date, in `YYYY-MM-DD` format. + + created_after : typing.Optional[dt.date] + Returns Campaigns created on or after this date, in `YYYY-MM-DD` format. + + page : typing.Optional[int] + Page number to retrieve. Default `1`. + + per_page : typing.Optional[int] + Number of records to return per page. Default `25`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ListByBrandCampaignsResponse] + Returns a paginated list of 10DLC Campaigns. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns", + method="GET", + params={ + "name": name, + "usecase": usecase, + "status": status, + "mock": mock, + "created_before": str(created_before) if created_before is not None else None, + "created_after": str(created_after) if created_after is not None else None, + "page": page, + "per_page": per_page, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ListByBrandCampaignsResponse, + parse_obj_as( + type_=ListByBrandCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + brand_id: str, + *, + affiliate_marketing: bool, + age_gated: bool, + auto_renewal: bool, + direct_lending: bool, + embedded_links: bool, + description: str, + optin_workflow: str, + help: bool, + help_keywords: str, + help_message: str, + optin: bool, + optin_keywords: str, + optin_message: str, + optout: bool, + optout_keywords: str, + optout_message: str, + name: str, + sample1: str, + mock: bool, + usecase: str, + terms_conditions: str, + embedded_phones: typing.Optional[bool] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[CreateCampaignsResponse]: + """ + Registers a 10DLC Campaign under the 10DLC Brand identified by `brand_id`. The Brand must have a verified identity status. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + affiliate_marketing : bool + Indicates whether the Campaign is used for affiliate marketing. + + age_gated : bool + Indicates whether the Campaign messages contain age-gated content. + + auto_renewal : bool + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + direct_lending : bool + Indicates whether the Campaign messages contain direct lending content. + + embedded_links : bool + Indicates whether the Campaign messages contain embedded links. + + description : str + Description of the Campaign and its messaging purpose. + + optin_workflow : str + Description of the workflow through which subscribers opt in to the Campaign. + + help : bool + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword such as HELP or INFO. + + help_keywords : str + Comma-separated list of help keywords. Keywords are case-insensitive. + + help_message : str + Acknowledgement sent when a subscriber texts a help keyword. + + optin : bool + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optin_keywords : str + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + optin_message : str + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout : bool + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword such as STOP or QUIT. + + optout_keywords : str + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + optout_message : str + Acknowledgement sent when a subscriber texts an opt-out keyword. + + name : str + Display name of the Campaign. + + sample1 : str + Sample message demonstrating the content sent through the Campaign. + + mock : bool + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + + usecase : str + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + + terms_conditions : str + URL of the Campaign terms and conditions. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateCampaignsResponse] + Returns the registered 10DLC Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns", + method="POST", + json={ + "affiliate_marketing": affiliate_marketing, + "age_gated": age_gated, + "auto_renewal": auto_renewal, + "direct_lending": direct_lending, + "embedded_links": embedded_links, + "embedded_phones": embedded_phones, + "embedded_link_sample": embedded_link_sample, + "description": description, + "optin_workflow": optin_workflow, + "help": help, + "help_keywords": help_keywords, + "help_message": help_message, + "optin": optin, + "optin_keywords": optin_keywords, + "optin_message": optin_message, + "optout": optout, + "optout_keywords": optout_keywords, + "optout_message": optout_message, + "name": name, + "sample1": sample1, + "sample2": sample2, + "sample3": sample3, + "sample4": sample4, + "sample5": sample5, + "mock": mock, + "usecase": usecase, + "privacy_policy": privacy_policy, + "terms_conditions": terms_conditions, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateCampaignsResponse, + parse_obj_as( + type_=CreateCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[GetCampaignsResponse]: + """ + Returns the 10DLC Campaign identified by `campaign_id` under the Brand identified by `brand_id`. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GetCampaignsResponse] + Returns the 10DLC Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GetCampaignsResponse, + parse_obj_as( + type_=GetCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, + brand_id: str, + campaign_id: str, + *, + name: typing.Optional[str] = OMIT, + usecase: typing.Optional[TenDlcCampaignUpdateRequestUsecase] = OMIT, + description: typing.Optional[str] = OMIT, + embedded_links: typing.Optional[bool] = OMIT, + embedded_phones: typing.Optional[bool] = OMIT, + age_gated: typing.Optional[bool] = OMIT, + direct_lending: typing.Optional[bool] = OMIT, + optin: typing.Optional[bool] = OMIT, + optout: typing.Optional[bool] = OMIT, + help: typing.Optional[bool] = OMIT, + sample1: typing.Optional[str] = OMIT, + sample2: typing.Optional[str] = OMIT, + sample3: typing.Optional[str] = OMIT, + sample4: typing.Optional[str] = OMIT, + sample5: typing.Optional[str] = OMIT, + optin_workflow: typing.Optional[str] = OMIT, + help_message: typing.Optional[str] = OMIT, + optin_message: typing.Optional[str] = OMIT, + optout_message: typing.Optional[str] = OMIT, + auto_renewal: typing.Optional[bool] = OMIT, + optin_keywords: typing.Optional[str] = OMIT, + help_keywords: typing.Optional[str] = OMIT, + optout_keywords: typing.Optional[str] = OMIT, + terms_conditions: typing.Optional[str] = OMIT, + privacy_policy: typing.Optional[str] = OMIT, + embedded_link_sample: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[UpdateCampaignsResponse]: + """ + Updates the 10DLC Campaign identified by `campaign_id`. Only the provided fields are changed. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + name : typing.Optional[str] + Display name of the Campaign. + + usecase : typing.Optional[TenDlcCampaignUpdateRequestUsecase] + Registered use case for the Campaign. One of `CUSTOMER_CARE` (customer support messaging), `MARKETING` (promotional content), `ACCOUNT_NOTIFICATION` (account-related alerts), `FRAUD_ALERT` (fraud and suspicious-activity warnings), `PUBLIC_SERVICE_ANNOUNCEMENT` (public-interest notices), or `SECURITY_ALERT` (security-related warnings). + + description : typing.Optional[str] + Description of the Campaign and its messaging purpose. + + embedded_links : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded links. + + embedded_phones : typing.Optional[bool] + Indicates whether the Campaign messages contain embedded phone numbers. + + age_gated : typing.Optional[bool] + Indicates whether the Campaign messages contain age-gated content. + + direct_lending : typing.Optional[bool] + Indicates whether the Campaign messages contain direct lending content. + + optin : typing.Optional[bool] + Indicates whether the Campaign requires subscribers to opt in before receiving messages. + + optout : typing.Optional[bool] + Indicates whether the Campaign provides an opt-out system that subscribers can trigger with a keyword. + + help : typing.Optional[bool] + Indicates whether the Campaign provides a help system that subscribers can trigger with a keyword. + + sample1 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample2 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample3 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample4 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + sample5 : typing.Optional[str] + Sample message demonstrating the content sent through the Campaign. + + optin_workflow : typing.Optional[str] + Description of the workflow through which subscribers opt in to the Campaign. + + help_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts a help keyword. + + optin_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-in keyword. + + optout_message : typing.Optional[str] + Acknowledgement sent when a subscriber texts an opt-out keyword. + + auto_renewal : typing.Optional[bool] + Indicates whether the Campaign is automatically renewed at the end of each billing period. + + optin_keywords : typing.Optional[str] + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + + help_keywords : typing.Optional[str] + Comma-separated list of help keywords. Keywords are case-insensitive. + + optout_keywords : typing.Optional[str] + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + + terms_conditions : typing.Optional[str] + URL of the Campaign terms and conditions. + + privacy_policy : typing.Optional[str] + URL of the Campaign privacy policy. + + embedded_link_sample : typing.Optional[str] + Sample of an embedded link used in Campaign messages. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[UpdateCampaignsResponse] + Returns the updated 10DLC Campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="PUT", + json={ + "name": name, + "usecase": usecase, + "description": description, + "embedded_links": embedded_links, + "embedded_phones": embedded_phones, + "age_gated": age_gated, + "direct_lending": direct_lending, + "optin": optin, + "optout": optout, + "help": help, + "sample1": sample1, + "sample2": sample2, + "sample3": sample3, + "sample4": sample4, + "sample5": sample5, + "optin_workflow": optin_workflow, + "help_message": help_message, + "optin_message": optin_message, + "optout_message": optout_message, + "auto_renewal": auto_renewal, + "optin_keywords": optin_keywords, + "help_keywords": help_keywords, + "optout_keywords": optout_keywords, + "terms_conditions": terms_conditions, + "privacy_policy": privacy_policy, + "embedded_link_sample": embedded_link_sample, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + UpdateCampaignsResponse, + parse_obj_as( + type_=UpdateCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, brand_id: str, campaign_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteCampaignsResponse]: + """ + Deletes a 10DLC Campaign. Associated phone numbers cannot be used as Sender IDs once the Campaign is deleted. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteCampaignsResponse] + Returns a success confirmation. The 10DLC Campaign is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteCampaignsResponse, + parse_obj_as( + type_=DeleteCampaignsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def nudge( + self, + brand_id: str, + campaign_id: str, + *, + nudge_intent: str, + description: str, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Requests action on a pending or rejected 10DLC Campaign. Use `nudge_intent` to specify the action: + - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + Note: + - The Campaign must be at least 72 hours old. + - Only one nudge request per Campaign is allowed every 24 hours. + + Parameters + ---------- + brand_id : str + The unique ID of the 10DLC Brand. + + campaign_id : str + The unique ID of the 10DLC Campaign. + + nudge_intent : str + Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. + Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + + description : str + Description of the nudge request. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The nudge request is submitted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v3/10dlc/brands/{encode_path_param(brand_id)}/campaigns/{encode_path_param(campaign_id)}/nudge", + method="POST", + json={ + "nudge_intent": nudge_intent, + "description": description, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + ValidationErrorResponse, + parse_obj_as( + type_=ValidationErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/campaigns/types/__init__.py b/src/wavix/ten_dlc/campaigns/types/__init__.py new file mode 100644 index 0000000..af8fae8 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/__init__.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_campaigns_response import CreateCampaignsResponse + from .delete_campaigns_response import DeleteCampaignsResponse + from .get_campaigns_response import GetCampaignsResponse + from .list_by_brand_campaigns_response import ListByBrandCampaignsResponse + from .list_by_brand_campaigns_response_pagination import ListByBrandCampaignsResponsePagination + from .list_campaigns_response import ListCampaignsResponse + from .list_campaigns_response_pagination import ListCampaignsResponsePagination + from .ten_dlc_campaign_update_request_usecase import TenDlcCampaignUpdateRequestUsecase + from .update_campaigns_response import UpdateCampaignsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateCampaignsResponse": ".create_campaigns_response", + "DeleteCampaignsResponse": ".delete_campaigns_response", + "GetCampaignsResponse": ".get_campaigns_response", + "ListByBrandCampaignsResponse": ".list_by_brand_campaigns_response", + "ListByBrandCampaignsResponsePagination": ".list_by_brand_campaigns_response_pagination", + "ListCampaignsResponse": ".list_campaigns_response", + "ListCampaignsResponsePagination": ".list_campaigns_response_pagination", + "TenDlcCampaignUpdateRequestUsecase": ".ten_dlc_campaign_update_request_usecase", + "UpdateCampaignsResponse": ".update_campaigns_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateCampaignsResponse", + "DeleteCampaignsResponse", + "GetCampaignsResponse", + "ListByBrandCampaignsResponse", + "ListByBrandCampaignsResponsePagination", + "ListCampaignsResponse", + "ListCampaignsResponsePagination", + "TenDlcCampaignUpdateRequestUsecase", + "UpdateCampaignsResponse", +] diff --git a/src/wavix/ten_dlc/campaigns/types/create_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/create_campaigns_response.py new file mode 100644 index 0000000..8229008 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/create_campaigns_response.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateCampaignsResponse(UniversalBaseModel): + """ + Represents a 10DLC campaign registered under a Brand. A Campaign defines the messaging use case, opt-in and opt-out flows, and the phone numbers permitted to send its traffic. + """ + + affiliate_marketing: bool = pydantic.Field() + """ + Indicates whether the Campaign is used for affiliate marketing. + """ + + age_gated: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain age-gated content. + """ + + auto_renewal: bool = pydantic.Field() + """ + Indicates whether the Campaign should be automatically renewed. + """ + + last_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign was last billed in ISO 8601 format. `null` if the Campaign has never been billed. + """ + + next_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign will be billed next in ISO 8601 format. `null` if the next billing date is not scheduled. + """ + + direct_lending: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain direct lending content. + """ + + embedded_links: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded links. + """ + + embedded_phones: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded phone numbers. + """ + + embedded_link_sample: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample of an embedded link used in Campaign messages. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the Campaign assigned by the registry. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the Campaign and its messaging purpose. + """ + + optin_workflow: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the workflow through which subscribers opt in to the Campaign. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the registry explaining the current `status`. + """ + + help: bool = pydantic.Field() + """ + Indicates whether the campaign includes a help system (for example, keyword: HELP, INFO). + """ + + help_keywords: str = pydantic.Field() + """ + Comma-separated list of help keywords. Keywords are case-insensitive. + """ + + help_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Help message sent upon receiving a help keyword. + """ + + optin: bool = pydantic.Field() + """ + Indicates whether the Campaign requires subscriber opt-in. + """ + + optin_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + """ + + optin_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-in message sent upon receiving an opt-in keyword. + """ + + optout: bool = pydantic.Field() + """ + Indicates whether the campaign includes an opt-out system (for example, keyword: STOP, QUIT). + """ + + optout_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + """ + + optout_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-out message sent upon receiving an opt-out keyword. + """ + + name: str = pydantic.Field() + """ + Display name of the Campaign. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Campaign was created, in ISO 8601 format. + """ + + sample1: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample2: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample3: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample4: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample5: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Campaign was last updated, in ISO 8601 format. + """ + + mock: bool = pydantic.Field() + """ + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + """ + + usecase: str = pydantic.Field() + """ + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + """ + + monthly_fee: str = pydantic.Field() + """ + Recurring monthly fee charged for the Campaign, as a decimal string. + """ + + privacy_policy: typing.Optional[str] = pydantic.Field(default=None) + """ + Privacy policy URL. + """ + + terms_conditions: typing.Optional[str] = pydantic.Field(default=None) + """ + Terms and conditions URL. + """ + + status: str = pydantic.Field() + """ + Current registration status of the Campaign, such as `APPROVED` or `PENDING`. + """ + + phone_numbers: typing.List[str] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/delete_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/delete_campaigns_response.py new file mode 100644 index 0000000..9b721cf --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/delete_campaigns_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteCampaignsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/get_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/get_campaigns_response.py new file mode 100644 index 0000000..b3a63e9 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/get_campaigns_response.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GetCampaignsResponse(UniversalBaseModel): + """ + Represents a 10DLC campaign registered under a Brand. A Campaign defines the messaging use case, opt-in and opt-out flows, and the phone numbers permitted to send its traffic. + """ + + affiliate_marketing: bool = pydantic.Field() + """ + Indicates whether the Campaign is used for affiliate marketing. + """ + + age_gated: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain age-gated content. + """ + + auto_renewal: bool = pydantic.Field() + """ + Indicates whether the Campaign should be automatically renewed. + """ + + last_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign was last billed in ISO 8601 format. `null` if the Campaign has never been billed. + """ + + next_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign will be billed next in ISO 8601 format. `null` if the next billing date is not scheduled. + """ + + direct_lending: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain direct lending content. + """ + + embedded_links: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded links. + """ + + embedded_phones: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded phone numbers. + """ + + embedded_link_sample: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample of an embedded link used in Campaign messages. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the Campaign assigned by the registry. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the Campaign and its messaging purpose. + """ + + optin_workflow: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the workflow through which subscribers opt in to the Campaign. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the registry explaining the current `status`. + """ + + help: bool = pydantic.Field() + """ + Indicates whether the campaign includes a help system (for example, keyword: HELP, INFO). + """ + + help_keywords: str = pydantic.Field() + """ + Comma-separated list of help keywords. Keywords are case-insensitive. + """ + + help_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Help message sent upon receiving a help keyword. + """ + + optin: bool = pydantic.Field() + """ + Indicates whether the Campaign requires subscriber opt-in. + """ + + optin_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + """ + + optin_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-in message sent upon receiving an opt-in keyword. + """ + + optout: bool = pydantic.Field() + """ + Indicates whether the campaign includes an opt-out system (for example, keyword: STOP, QUIT). + """ + + optout_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + """ + + optout_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-out message sent upon receiving an opt-out keyword. + """ + + name: str = pydantic.Field() + """ + Display name of the Campaign. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Campaign was created, in ISO 8601 format. + """ + + sample1: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample2: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample3: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample4: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample5: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Campaign was last updated, in ISO 8601 format. + """ + + mock: bool = pydantic.Field() + """ + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + """ + + usecase: str = pydantic.Field() + """ + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + """ + + monthly_fee: str = pydantic.Field() + """ + Recurring monthly fee charged for the Campaign, as a decimal string. + """ + + privacy_policy: typing.Optional[str] = pydantic.Field(default=None) + """ + Privacy policy URL. + """ + + terms_conditions: typing.Optional[str] = pydantic.Field(default=None) + """ + Terms and conditions URL. + """ + + status: str = pydantic.Field() + """ + Current registration status of the Campaign, such as `APPROVED` or `PENDING`. + """ + + phone_numbers: typing.List[str] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response.py new file mode 100644 index 0000000..beedf3a --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.ten_dlc_campaign import TenDlcCampaign +from .list_by_brand_campaigns_response_pagination import ListByBrandCampaignsResponsePagination + + +class ListByBrandCampaignsResponse(UniversalBaseModel): + """ + Paginated list of 10DLC Campaigns. + """ + + items: typing.List[TenDlcCampaign] = pydantic.Field() + """ + 10DLC Campaigns on the current page that match the filter criteria. + """ + + pagination: ListByBrandCampaignsResponsePagination = pydantic.Field() + """ + Pagination metadata for the result set. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response_pagination.py b/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response_pagination.py new file mode 100644 index 0000000..dcf6661 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/list_by_brand_campaigns_response_pagination.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ListByBrandCampaignsResponsePagination(UniversalBaseModel): + """ + Pagination metadata for the result set. + """ + + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/list_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/list_campaigns_response.py new file mode 100644 index 0000000..defe2f7 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/list_campaigns_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.ten_dlc_campaign import TenDlcCampaign +from .list_campaigns_response_pagination import ListCampaignsResponsePagination + + +class ListCampaignsResponse(UniversalBaseModel): + """ + Paginated list of 10DLC Campaigns. + """ + + items: typing.List[TenDlcCampaign] = pydantic.Field() + """ + 10DLC Campaigns on the current page that match the filter criteria. + """ + + pagination: ListCampaignsResponsePagination = pydantic.Field() + """ + Pagination metadata for the result set. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/list_campaigns_response_pagination.py b/src/wavix/ten_dlc/campaigns/types/list_campaigns_response_pagination.py new file mode 100644 index 0000000..53261c1 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/list_campaigns_response_pagination.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ListCampaignsResponsePagination(UniversalBaseModel): + """ + Pagination metadata for the result set. + """ + + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/campaigns/types/ten_dlc_campaign_update_request_usecase.py b/src/wavix/ten_dlc/campaigns/types/ten_dlc_campaign_update_request_usecase.py new file mode 100644 index 0000000..73fb2ad --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/ten_dlc_campaign_update_request_usecase.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcCampaignUpdateRequestUsecase = typing.Union[ + typing.Literal[ + "CUSTOMER_CARE", + "MARKETING", + "ACCOUNT_NOTIFICATION", + "FRAUD_ALERT", + "PUBLIC_SERVICE_ANNOUNCEMENT", + "SECURITY_ALERT", + ], + typing.Any, +] diff --git a/src/wavix/ten_dlc/campaigns/types/update_campaigns_response.py b/src/wavix/ten_dlc/campaigns/types/update_campaigns_response.py new file mode 100644 index 0000000..7367d72 --- /dev/null +++ b/src/wavix/ten_dlc/campaigns/types/update_campaigns_response.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UpdateCampaignsResponse(UniversalBaseModel): + """ + Represents a 10DLC campaign registered under a Brand. A Campaign defines the messaging use case, opt-in and opt-out flows, and the phone numbers permitted to send its traffic. + """ + + affiliate_marketing: bool = pydantic.Field() + """ + Indicates whether the Campaign is used for affiliate marketing. + """ + + age_gated: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain age-gated content. + """ + + auto_renewal: bool = pydantic.Field() + """ + Indicates whether the Campaign should be automatically renewed. + """ + + last_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign was last billed in ISO 8601 format. `null` if the Campaign has never been billed. + """ + + next_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign will be billed next in ISO 8601 format. `null` if the next billing date is not scheduled. + """ + + direct_lending: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain direct lending content. + """ + + embedded_links: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded links. + """ + + embedded_phones: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded phone numbers. + """ + + embedded_link_sample: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample of an embedded link used in Campaign messages. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the Campaign assigned by the registry. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the Campaign and its messaging purpose. + """ + + optin_workflow: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the workflow through which subscribers opt in to the Campaign. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the registry explaining the current `status`. + """ + + help: bool = pydantic.Field() + """ + Indicates whether the campaign includes a help system (for example, keyword: HELP, INFO). + """ + + help_keywords: str = pydantic.Field() + """ + Comma-separated list of help keywords. Keywords are case-insensitive. + """ + + help_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Help message sent upon receiving a help keyword. + """ + + optin: bool = pydantic.Field() + """ + Indicates whether the Campaign requires subscriber opt-in. + """ + + optin_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + """ + + optin_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-in message sent upon receiving an opt-in keyword. + """ + + optout: bool = pydantic.Field() + """ + Indicates whether the campaign includes an opt-out system (for example, keyword: STOP, QUIT). + """ + + optout_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + """ + + optout_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-out message sent upon receiving an opt-out keyword. + """ + + name: str = pydantic.Field() + """ + Display name of the Campaign. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Campaign was created, in ISO 8601 format. + """ + + sample1: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample2: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample3: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample4: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample5: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Campaign was last updated, in ISO 8601 format. + """ + + mock: bool = pydantic.Field() + """ + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + """ + + usecase: str = pydantic.Field() + """ + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + """ + + monthly_fee: str = pydantic.Field() + """ + Recurring monthly fee charged for the Campaign, as a decimal string. + """ + + privacy_policy: typing.Optional[str] = pydantic.Field(default=None) + """ + Privacy policy URL. + """ + + terms_conditions: typing.Optional[str] = pydantic.Field(default=None) + """ + Terms and conditions URL. + """ + + status: str = pydantic.Field() + """ + Current registration status of the Campaign, such as `APPROVED` or `PENDING`. + """ + + phone_numbers: typing.List[str] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/client.py b/src/wavix/ten_dlc/client.py new file mode 100644 index 0000000..73553c1 --- /dev/null +++ b/src/wavix/ten_dlc/client.py @@ -0,0 +1,196 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawTenDlcClient, RawTenDlcClient + +if typing.TYPE_CHECKING: + from .brand_appeals.client import AsyncBrandAppealsClient, BrandAppealsClient + from .brand_evidence.client import AsyncBrandEvidenceClient, BrandEvidenceClient + from .brand_vetting_appeals.client import AsyncBrandVettingAppealsClient, BrandVettingAppealsClient + from .brand_vettings.client import AsyncBrandVettingsClient, BrandVettingsClient + from .brands.client import AsyncBrandsClient, BrandsClient + from .campaign_numbers.client import AsyncCampaignNumbersClient, CampaignNumbersClient + from .campaigns.client import AsyncCampaignsClient, CampaignsClient + from .subscriptions.client import AsyncSubscriptionsClient, SubscriptionsClient + + +class TenDlcClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTenDlcClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._brands: typing.Optional[BrandsClient] = None + self._brand_appeals: typing.Optional[BrandAppealsClient] = None + self._brand_evidence: typing.Optional[BrandEvidenceClient] = None + self._brand_vettings: typing.Optional[BrandVettingsClient] = None + self._brand_vetting_appeals: typing.Optional[BrandVettingAppealsClient] = None + self._campaigns: typing.Optional[CampaignsClient] = None + self._subscriptions: typing.Optional[SubscriptionsClient] = None + self._campaign_numbers: typing.Optional[CampaignNumbersClient] = None + + @property + def with_raw_response(self) -> RawTenDlcClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTenDlcClient + """ + return self._raw_client + + @property + def brands(self): + if self._brands is None: + from .brands.client import BrandsClient # noqa: E402 + + self._brands = BrandsClient(client_wrapper=self._client_wrapper) + return self._brands + + @property + def brand_appeals(self): + if self._brand_appeals is None: + from .brand_appeals.client import BrandAppealsClient # noqa: E402 + + self._brand_appeals = BrandAppealsClient(client_wrapper=self._client_wrapper) + return self._brand_appeals + + @property + def brand_evidence(self): + if self._brand_evidence is None: + from .brand_evidence.client import BrandEvidenceClient # noqa: E402 + + self._brand_evidence = BrandEvidenceClient(client_wrapper=self._client_wrapper) + return self._brand_evidence + + @property + def brand_vettings(self): + if self._brand_vettings is None: + from .brand_vettings.client import BrandVettingsClient # noqa: E402 + + self._brand_vettings = BrandVettingsClient(client_wrapper=self._client_wrapper) + return self._brand_vettings + + @property + def brand_vetting_appeals(self): + if self._brand_vetting_appeals is None: + from .brand_vetting_appeals.client import BrandVettingAppealsClient # noqa: E402 + + self._brand_vetting_appeals = BrandVettingAppealsClient(client_wrapper=self._client_wrapper) + return self._brand_vetting_appeals + + @property + def campaigns(self): + if self._campaigns is None: + from .campaigns.client import CampaignsClient # noqa: E402 + + self._campaigns = CampaignsClient(client_wrapper=self._client_wrapper) + return self._campaigns + + @property + def subscriptions(self): + if self._subscriptions is None: + from .subscriptions.client import SubscriptionsClient # noqa: E402 + + self._subscriptions = SubscriptionsClient(client_wrapper=self._client_wrapper) + return self._subscriptions + + @property + def campaign_numbers(self): + if self._campaign_numbers is None: + from .campaign_numbers.client import CampaignNumbersClient # noqa: E402 + + self._campaign_numbers = CampaignNumbersClient(client_wrapper=self._client_wrapper) + return self._campaign_numbers + + +class AsyncTenDlcClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTenDlcClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._brands: typing.Optional[AsyncBrandsClient] = None + self._brand_appeals: typing.Optional[AsyncBrandAppealsClient] = None + self._brand_evidence: typing.Optional[AsyncBrandEvidenceClient] = None + self._brand_vettings: typing.Optional[AsyncBrandVettingsClient] = None + self._brand_vetting_appeals: typing.Optional[AsyncBrandVettingAppealsClient] = None + self._campaigns: typing.Optional[AsyncCampaignsClient] = None + self._subscriptions: typing.Optional[AsyncSubscriptionsClient] = None + self._campaign_numbers: typing.Optional[AsyncCampaignNumbersClient] = None + + @property + def with_raw_response(self) -> AsyncRawTenDlcClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTenDlcClient + """ + return self._raw_client + + @property + def brands(self): + if self._brands is None: + from .brands.client import AsyncBrandsClient # noqa: E402 + + self._brands = AsyncBrandsClient(client_wrapper=self._client_wrapper) + return self._brands + + @property + def brand_appeals(self): + if self._brand_appeals is None: + from .brand_appeals.client import AsyncBrandAppealsClient # noqa: E402 + + self._brand_appeals = AsyncBrandAppealsClient(client_wrapper=self._client_wrapper) + return self._brand_appeals + + @property + def brand_evidence(self): + if self._brand_evidence is None: + from .brand_evidence.client import AsyncBrandEvidenceClient # noqa: E402 + + self._brand_evidence = AsyncBrandEvidenceClient(client_wrapper=self._client_wrapper) + return self._brand_evidence + + @property + def brand_vettings(self): + if self._brand_vettings is None: + from .brand_vettings.client import AsyncBrandVettingsClient # noqa: E402 + + self._brand_vettings = AsyncBrandVettingsClient(client_wrapper=self._client_wrapper) + return self._brand_vettings + + @property + def brand_vetting_appeals(self): + if self._brand_vetting_appeals is None: + from .brand_vetting_appeals.client import AsyncBrandVettingAppealsClient # noqa: E402 + + self._brand_vetting_appeals = AsyncBrandVettingAppealsClient(client_wrapper=self._client_wrapper) + return self._brand_vetting_appeals + + @property + def campaigns(self): + if self._campaigns is None: + from .campaigns.client import AsyncCampaignsClient # noqa: E402 + + self._campaigns = AsyncCampaignsClient(client_wrapper=self._client_wrapper) + return self._campaigns + + @property + def subscriptions(self): + if self._subscriptions is None: + from .subscriptions.client import AsyncSubscriptionsClient # noqa: E402 + + self._subscriptions = AsyncSubscriptionsClient(client_wrapper=self._client_wrapper) + return self._subscriptions + + @property + def campaign_numbers(self): + if self._campaign_numbers is None: + from .campaign_numbers.client import AsyncCampaignNumbersClient # noqa: E402 + + self._campaign_numbers = AsyncCampaignNumbersClient(client_wrapper=self._client_wrapper) + return self._campaign_numbers diff --git a/src/wavix/ten_dlc/raw_client.py b/src/wavix/ten_dlc/raw_client.py new file mode 100644 index 0000000..8ee0a57 --- /dev/null +++ b/src/wavix/ten_dlc/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawTenDlcClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawTenDlcClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/ten_dlc/subscriptions/__init__.py b/src/wavix/ten_dlc/subscriptions/__init__.py new file mode 100644 index 0000000..fce3a1f --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/__init__.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import CreateSubscriptionsResponse, DeleteSubscriptionsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateSubscriptionsResponse": ".types", + "DeleteSubscriptionsResponse": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateSubscriptionsResponse", "DeleteSubscriptionsResponse"] diff --git a/src/wavix/ten_dlc/subscriptions/client.py b/src/wavix/ten_dlc/subscriptions/client.py new file mode 100644 index 0000000..c089df1 --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/client.py @@ -0,0 +1,271 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.ten_dlc_event_subscription import TenDlcEventSubscription +from .raw_client import AsyncRawSubscriptionsClient, RawSubscriptionsClient +from .types.create_subscriptions_response import CreateSubscriptionsResponse +from .types.delete_subscriptions_response import DeleteSubscriptionsResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class SubscriptionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSubscriptionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSubscriptionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSubscriptionsClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[TenDlcEventSubscription]: + """ + Returns the 10DLC event subscriptions for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcEventSubscription] + Returns the list of 10DLC event subscriptions. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.subscriptions.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, *, subscription_category: str, url: str, request_options: typing.Optional[RequestOptions] = None + ) -> CreateSubscriptionsResponse: + """ + Registers a callback URL to receive Wavix 10DLC event notifications. + + Parameters + ---------- + subscription_category : str + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + + url : str + Webhook URL that events in this category are delivered to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateSubscriptionsResponse + Returns the created 10DLC event subscription. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.subscriptions.create( + subscription_category="brand", + url="https://webhook.url", + ) + """ + _response = self._raw_client.create( + subscription_category=subscription_category, url=url, request_options=request_options + ) + return _response.data + + def delete( + self, *, subscription_category: str, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteSubscriptionsResponse: + """ + Removes the 10DLC event subscription for the specified event category. + + Parameters + ---------- + subscription_category : str + Event category to unsubscribe from. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteSubscriptionsResponse + Returns a success confirmation. The event subscription is removed. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.ten_dlc.subscriptions.delete( + subscription_category="number", + ) + """ + _response = self._raw_client.delete( + subscription_category=subscription_category, request_options=request_options + ) + return _response.data + + +class AsyncSubscriptionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSubscriptionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSubscriptionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSubscriptionsClient + """ + return self._raw_client + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TenDlcEventSubscription]: + """ + Returns the 10DLC event subscriptions for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TenDlcEventSubscription] + Returns the list of 10DLC event subscriptions. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.subscriptions.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, *, subscription_category: str, url: str, request_options: typing.Optional[RequestOptions] = None + ) -> CreateSubscriptionsResponse: + """ + Registers a callback URL to receive Wavix 10DLC event notifications. + + Parameters + ---------- + subscription_category : str + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + + url : str + Webhook URL that events in this category are delivered to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateSubscriptionsResponse + Returns the created 10DLC event subscription. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.subscriptions.create( + subscription_category="brand", + url="https://webhook.url", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + subscription_category=subscription_category, url=url, request_options=request_options + ) + return _response.data + + async def delete( + self, *, subscription_category: str, request_options: typing.Optional[RequestOptions] = None + ) -> DeleteSubscriptionsResponse: + """ + Removes the 10DLC event subscription for the specified event category. + + Parameters + ---------- + subscription_category : str + Event category to unsubscribe from. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DeleteSubscriptionsResponse + Returns a success confirmation. The event subscription is removed. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.ten_dlc.subscriptions.delete( + subscription_category="number", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete( + subscription_category=subscription_category, request_options=request_options + ) + return _response.data diff --git a/src/wavix/ten_dlc/subscriptions/raw_client.py b/src/wavix/ten_dlc/subscriptions/raw_client.py new file mode 100644 index 0000000..dea33fa --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/raw_client.py @@ -0,0 +1,398 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...types.ten_dlc_event_subscription import TenDlcEventSubscription +from .types.create_subscriptions_response import CreateSubscriptionsResponse +from .types.delete_subscriptions_response import DeleteSubscriptionsResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawSubscriptionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[TenDlcEventSubscription]]: + """ + Returns the 10DLC event subscriptions for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[TenDlcEventSubscription]] + Returns the list of 10DLC event subscriptions. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcEventSubscription], + parse_obj_as( + type_=typing.List[TenDlcEventSubscription], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, *, subscription_category: str, url: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CreateSubscriptionsResponse]: + """ + Registers a callback URL to receive Wavix 10DLC event notifications. + + Parameters + ---------- + subscription_category : str + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + + url : str + Webhook URL that events in this category are delivered to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateSubscriptionsResponse] + Returns the created 10DLC event subscription. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="POST", + json={ + "subscription_category": subscription_category, + "url": url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateSubscriptionsResponse, + parse_obj_as( + type_=CreateSubscriptionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, *, subscription_category: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[DeleteSubscriptionsResponse]: + """ + Removes the 10DLC event subscription for the specified event category. + + Parameters + ---------- + subscription_category : str + Event category to unsubscribe from. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DeleteSubscriptionsResponse] + Returns a success confirmation. The event subscription is removed. + """ + _response = self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="DELETE", + params={ + "subscription_category": subscription_category, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteSubscriptionsResponse, + parse_obj_as( + type_=DeleteSubscriptionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSubscriptionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[TenDlcEventSubscription]]: + """ + Returns the 10DLC event subscriptions for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[TenDlcEventSubscription]] + Returns the list of 10DLC event subscriptions. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TenDlcEventSubscription], + parse_obj_as( + type_=typing.List[TenDlcEventSubscription], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, *, subscription_category: str, url: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreateSubscriptionsResponse]: + """ + Registers a callback URL to receive Wavix 10DLC event notifications. + + Parameters + ---------- + subscription_category : str + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + + url : str + Webhook URL that events in this category are delivered to. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateSubscriptionsResponse] + Returns the created 10DLC event subscription. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="POST", + json={ + "subscription_category": subscription_category, + "url": url, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateSubscriptionsResponse, + parse_obj_as( + type_=CreateSubscriptionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, *, subscription_category: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[DeleteSubscriptionsResponse]: + """ + Removes the 10DLC event subscription for the specified event category. + + Parameters + ---------- + subscription_category : str + Event category to unsubscribe from. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DeleteSubscriptionsResponse] + Returns a success confirmation. The event subscription is removed. + """ + _response = await self._client_wrapper.httpx_client.request( + "v3/10dlc/subscriptions", + method="DELETE", + params={ + "subscription_category": subscription_category, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DeleteSubscriptionsResponse, + parse_obj_as( + type_=DeleteSubscriptionsResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/ten_dlc/subscriptions/types/__init__.py b/src/wavix/ten_dlc/subscriptions/types/__init__.py new file mode 100644 index 0000000..5fc752a --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/types/__init__.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_subscriptions_response import CreateSubscriptionsResponse + from .delete_subscriptions_response import DeleteSubscriptionsResponse +_dynamic_imports: typing.Dict[str, str] = { + "CreateSubscriptionsResponse": ".create_subscriptions_response", + "DeleteSubscriptionsResponse": ".delete_subscriptions_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["CreateSubscriptionsResponse", "DeleteSubscriptionsResponse"] diff --git a/src/wavix/ten_dlc/subscriptions/types/create_subscriptions_response.py b/src/wavix/ten_dlc/subscriptions/types/create_subscriptions_response.py new file mode 100644 index 0000000..a477b00 --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/types/create_subscriptions_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateSubscriptionsResponse(UniversalBaseModel): + """ + Represents a subscription that delivers 10DLC lifecycle events to a webhook URL. + """ + + subscription_category: str = pydantic.Field() + """ + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + """ + + url: str = pydantic.Field() + """ + Webhook URL that events in this category are delivered to. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/ten_dlc/subscriptions/types/delete_subscriptions_response.py b/src/wavix/ten_dlc/subscriptions/types/delete_subscriptions_response.py new file mode 100644 index 0000000..fb1d9c6 --- /dev/null +++ b/src/wavix/ten_dlc/subscriptions/types/delete_subscriptions_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeleteSubscriptionsResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/two_fa/__init__.py b/src/wavix/two_fa/__init__.py new file mode 100644 index 0000000..8b0948a --- /dev/null +++ b/src/wavix/two_fa/__init__.py @@ -0,0 +1,56 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import events, sessions, verification + from .verification import ( + CheckVerificationResponse, + CreateVerificationResponse, + ResendVerificationResponse, + TwoFactorVerificationResendRequestChannel, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CheckVerificationResponse": ".verification", + "CreateVerificationResponse": ".verification", + "ResendVerificationResponse": ".verification", + "TwoFactorVerificationResendRequestChannel": ".verification", + "events": ".events", + "sessions": ".sessions", + "verification": ".verification", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CheckVerificationResponse", + "CreateVerificationResponse", + "ResendVerificationResponse", + "TwoFactorVerificationResendRequestChannel", + "events", + "sessions", + "verification", +] diff --git a/src/wavix/two_fa/client.py b/src/wavix/two_fa/client.py new file mode 100644 index 0000000..848443a --- /dev/null +++ b/src/wavix/two_fa/client.py @@ -0,0 +1,101 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawTwoFaClient, RawTwoFaClient + +if typing.TYPE_CHECKING: + from .events.client import AsyncEventsClient, EventsClient + from .sessions.client import AsyncSessionsClient, SessionsClient + from .verification.client import AsyncVerificationClient, VerificationClient + + +class TwoFaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTwoFaClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._verification: typing.Optional[VerificationClient] = None + self._sessions: typing.Optional[SessionsClient] = None + self._events: typing.Optional[EventsClient] = None + + @property + def with_raw_response(self) -> RawTwoFaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTwoFaClient + """ + return self._raw_client + + @property + def verification(self): + if self._verification is None: + from .verification.client import VerificationClient # noqa: E402 + + self._verification = VerificationClient(client_wrapper=self._client_wrapper) + return self._verification + + @property + def sessions(self): + if self._sessions is None: + from .sessions.client import SessionsClient # noqa: E402 + + self._sessions = SessionsClient(client_wrapper=self._client_wrapper) + return self._sessions + + @property + def events(self): + if self._events is None: + from .events.client import EventsClient # noqa: E402 + + self._events = EventsClient(client_wrapper=self._client_wrapper) + return self._events + + +class AsyncTwoFaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTwoFaClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._verification: typing.Optional[AsyncVerificationClient] = None + self._sessions: typing.Optional[AsyncSessionsClient] = None + self._events: typing.Optional[AsyncEventsClient] = None + + @property + def with_raw_response(self) -> AsyncRawTwoFaClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTwoFaClient + """ + return self._raw_client + + @property + def verification(self): + if self._verification is None: + from .verification.client import AsyncVerificationClient # noqa: E402 + + self._verification = AsyncVerificationClient(client_wrapper=self._client_wrapper) + return self._verification + + @property + def sessions(self): + if self._sessions is None: + from .sessions.client import AsyncSessionsClient # noqa: E402 + + self._sessions = AsyncSessionsClient(client_wrapper=self._client_wrapper) + return self._sessions + + @property + def events(self): + if self._events is None: + from .events.client import AsyncEventsClient # noqa: E402 + + self._events = AsyncEventsClient(client_wrapper=self._client_wrapper) + return self._events diff --git a/src/wavix/two_fa/events/__init__.py b/src/wavix/two_fa/events/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/two_fa/events/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/two_fa/events/client.py b/src/wavix/two_fa/events/client.py new file mode 100644 index 0000000..2fcc37f --- /dev/null +++ b/src/wavix/two_fa/events/client.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.two_factor_verification_event import TwoFactorVerificationEvent +from .raw_client import AsyncRawEventsClient, RawEventsClient + + +class EventsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawEventsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawEventsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawEventsClient + """ + return self._raw_client + + def list( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TwoFactorVerificationEvent]: + """ + Returns the lifecycle events of the 2FA verification identified by `session_id`, such as number lookup and code delivery. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TwoFactorVerificationEvent] + Returns the list of 2FA verification events. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.events.list( + session_id="8753d4308f2e11ecb75fcdafd6d2d690", + ) + """ + _response = self._raw_client.list(session_id, request_options=request_options) + return _response.data + + +class AsyncEventsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawEventsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawEventsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawEventsClient + """ + return self._raw_client + + async def list( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[TwoFactorVerificationEvent]: + """ + Returns the lifecycle events of the 2FA verification identified by `session_id`, such as number lookup and code delivery. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[TwoFactorVerificationEvent] + Returns the list of 2FA verification events. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.events.list( + session_id="8753d4308f2e11ecb75fcdafd6d2d690", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(session_id, request_options=request_options) + return _response.data diff --git a/src/wavix/two_fa/events/raw_client.py b/src/wavix/two_fa/events/raw_client.py new file mode 100644 index 0000000..5ccfc19 --- /dev/null +++ b/src/wavix/two_fa/events/raw_client.py @@ -0,0 +1,156 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.two_factor_verification_event import TwoFactorVerificationEvent +from pydantic import ValidationError + + +class RawEventsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[TwoFactorVerificationEvent]]: + """ + Returns the lifecycle events of the 2FA verification identified by `session_id`, such as number lookup and code delivery. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[TwoFactorVerificationEvent]] + Returns the list of 2FA verification events. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/two-fa/session/{encode_path_param(session_id)}/events", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TwoFactorVerificationEvent], + parse_obj_as( + type_=typing.List[TwoFactorVerificationEvent], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawEventsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[TwoFactorVerificationEvent]]: + """ + Returns the lifecycle events of the 2FA verification identified by `session_id`, such as number lookup and code delivery. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[TwoFactorVerificationEvent]] + Returns the list of 2FA verification events. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/two-fa/session/{encode_path_param(session_id)}/events", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[TwoFactorVerificationEvent], + parse_obj_as( + type_=typing.List[TwoFactorVerificationEvent], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/two_fa/raw_client.py b/src/wavix/two_fa/raw_client.py new file mode 100644 index 0000000..54e9e11 --- /dev/null +++ b/src/wavix/two_fa/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawTwoFaClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawTwoFaClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/two_fa/sessions/__init__.py b/src/wavix/two_fa/sessions/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/two_fa/sessions/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/two_fa/sessions/client.py b/src/wavix/two_fa/sessions/client.py new file mode 100644 index 0000000..7850b2f --- /dev/null +++ b/src/wavix/two_fa/sessions/client.py @@ -0,0 +1,142 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.list_sessions_response_item import ListSessionsResponseItem +from .raw_client import AsyncRawSessionsClient, RawSessionsClient + + +class SessionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawSessionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawSessionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawSessionsClient + """ + return self._raw_client + + def list( + self, service_id: str, *, from_: dt.date, to: dt.date, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[ListSessionsResponseItem]: + """ + Returns the 2FA verifications for the service identified by `service_id`, within the requested date range. + + Parameters + ---------- + service_id : str + The unique ID of the 2FA service. + + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[ListSessionsResponseItem] + Returns the list of 2FA verifications. + + Examples + -------- + import datetime + + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.sessions.list( + service_id="7204a030201211ee9fb47d093f2f127c", + from_=datetime.date.fromisoformat( + "2022-01-01", + ), + to=datetime.date.fromisoformat( + "2022-01-31", + ), + ) + """ + _response = self._raw_client.list(service_id, from_=from_, to=to, request_options=request_options) + return _response.data + + +class AsyncSessionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawSessionsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawSessionsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawSessionsClient + """ + return self._raw_client + + async def list( + self, service_id: str, *, from_: dt.date, to: dt.date, request_options: typing.Optional[RequestOptions] = None + ) -> typing.List[ListSessionsResponseItem]: + """ + Returns the 2FA verifications for the service identified by `service_id`, within the requested date range. + + Parameters + ---------- + service_id : str + The unique ID of the 2FA service. + + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.List[ListSessionsResponseItem] + Returns the list of 2FA verifications. + + Examples + -------- + import asyncio + import datetime + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.sessions.list( + service_id="7204a030201211ee9fb47d093f2f127c", + from_=datetime.date.fromisoformat( + "2022-01-01", + ), + to=datetime.date.fromisoformat( + "2022-01-31", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(service_id, from_=from_, to=to, request_options=request_options) + return _response.data diff --git a/src/wavix/two_fa/sessions/raw_client.py b/src/wavix/two_fa/sessions/raw_client.py new file mode 100644 index 0000000..eaccd62 --- /dev/null +++ b/src/wavix/two_fa/sessions/raw_client.py @@ -0,0 +1,200 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.list_sessions_response_item import ListSessionsResponseItem +from pydantic import ValidationError + + +class RawSessionsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, service_id: str, *, from_: dt.date, to: dt.date, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[typing.List[ListSessionsResponseItem]]: + """ + Returns the 2FA verifications for the service identified by `service_id`, within the requested date range. + + Parameters + ---------- + service_id : str + The unique ID of the 2FA service. + + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[typing.List[ListSessionsResponseItem]] + Returns the list of 2FA verifications. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/two-fa/service/{encode_path_param(service_id)}/sessions", + method="GET", + params={ + "from": str(from_), + "to": str(to), + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[ListSessionsResponseItem], + parse_obj_as( + type_=typing.List[ListSessionsResponseItem], # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawSessionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, service_id: str, *, from_: dt.date, to: dt.date, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[typing.List[ListSessionsResponseItem]]: + """ + Returns the 2FA verifications for the service identified by `service_id`, within the requested date range. + + Parameters + ---------- + service_id : str + The unique ID of the 2FA service. + + from_ : dt.date + Start of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + to : dt.date + End of the date range to query, in `YYYY-MM-DD` format. Inclusive. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.List[ListSessionsResponseItem]] + Returns the list of 2FA verifications. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/two-fa/service/{encode_path_param(service_id)}/sessions", + method="GET", + params={ + "from": str(from_), + "to": str(to), + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.List[ListSessionsResponseItem], + parse_obj_as( + type_=typing.List[ListSessionsResponseItem], # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/two_fa/verification/__init__.py b/src/wavix/two_fa/verification/__init__.py new file mode 100644 index 0000000..8988388 --- /dev/null +++ b/src/wavix/two_fa/verification/__init__.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CheckVerificationResponse, + CreateVerificationResponse, + ResendVerificationResponse, + TwoFactorVerificationResendRequestChannel, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CheckVerificationResponse": ".types", + "CreateVerificationResponse": ".types", + "ResendVerificationResponse": ".types", + "TwoFactorVerificationResendRequestChannel": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CheckVerificationResponse", + "CreateVerificationResponse", + "ResendVerificationResponse", + "TwoFactorVerificationResendRequestChannel", +] diff --git a/src/wavix/two_fa/verification/client.py b/src/wavix/two_fa/verification/client.py new file mode 100644 index 0000000..dddc593 --- /dev/null +++ b/src/wavix/two_fa/verification/client.py @@ -0,0 +1,395 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.success_response import SuccessResponse +from .raw_client import AsyncRawVerificationClient, RawVerificationClient +from .types.check_verification_response import CheckVerificationResponse +from .types.create_verification_response import CreateVerificationResponse +from .types.resend_verification_response import ResendVerificationResponse +from .types.two_factor_verification_resend_request_channel import TwoFactorVerificationResendRequestChannel + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class VerificationClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawVerificationClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawVerificationClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawVerificationClient + """ + return self._raw_client + + def create( + self, *, service_id: str, to: str, channel: str, request_options: typing.Optional[RequestOptions] = None + ) -> CreateVerificationResponse: + """ + Creates a 2FA verification and sends a one-time password (OTP) to the destination phone number over the selected channel. Requires a 2FA service configured in the Wavix portal; the service is reused to generate and validate OTPs. + + The verification proceeds through three steps: + 1. Create a verification to generate and send an OTP. + 2. Resend the OTP on the same verification if needed. + 3. Validate the OTP through the check endpoint. + + Parameters + ---------- + service_id : str + Unique Wavix 2FA Service ID. Available on the Wavix portal. + + to : str + End user's phone number to which the verification code will be sent. The phone number must be in E.164 format. + + channel : str + Channel used to deliver the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateVerificationResponse + Returns the created 2FA verification. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.verification.create( + service_id="7204a030201211ee9fb47d093f2f127c", + to="447919433768", + channel="sms", + ) + """ + _response = self._raw_client.create( + service_id=service_id, to=to, channel=channel, request_options=request_options + ) + return _response.data + + def resend( + self, + session_id: str, + *, + channel: TwoFactorVerificationResendRequestChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> ResendVerificationResponse: + """ + Resends the OTP for the verification identified by `session_id` over the specified channel. Previously sent codes are invalidated. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + channel : TwoFactorVerificationResendRequestChannel + Channel used to resend the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ResendVerificationResponse + Returns the resend result, including the channel used. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.verification.resend( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + channel="sms", + ) + """ + _response = self._raw_client.resend(session_id, channel=channel, request_options=request_options) + return _response.data + + def check( + self, session_id: str, *, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> CheckVerificationResponse: + """ + Validates the OTP submitted by the end user against the verification identified by `session_id`. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + code : str + The code entered by an end user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CheckVerificationResponse + Returns the validation result in `is_valid`. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.verification.check( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + code="123456", + ) + """ + _response = self._raw_client.check(session_id, code=code, request_options=request_options) + return _response.data + + def cancel(self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Cancels the 2FA verification identified by `session_id`. No further codes are sent, and previously sent codes can no longer be validated. A new verification is required to send another code. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The verification is canceled. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.two_fa.verification.cancel( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + ) + """ + _response = self._raw_client.cancel(session_id, request_options=request_options) + return _response.data + + +class AsyncVerificationClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawVerificationClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawVerificationClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawVerificationClient + """ + return self._raw_client + + async def create( + self, *, service_id: str, to: str, channel: str, request_options: typing.Optional[RequestOptions] = None + ) -> CreateVerificationResponse: + """ + Creates a 2FA verification and sends a one-time password (OTP) to the destination phone number over the selected channel. Requires a 2FA service configured in the Wavix portal; the service is reused to generate and validate OTPs. + + The verification proceeds through three steps: + 1. Create a verification to generate and send an OTP. + 2. Resend the OTP on the same verification if needed. + 3. Validate the OTP through the check endpoint. + + Parameters + ---------- + service_id : str + Unique Wavix 2FA Service ID. Available on the Wavix portal. + + to : str + End user's phone number to which the verification code will be sent. The phone number must be in E.164 format. + + channel : str + Channel used to deliver the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateVerificationResponse + Returns the created 2FA verification. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.verification.create( + service_id="7204a030201211ee9fb47d093f2f127c", + to="447919433768", + channel="sms", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + service_id=service_id, to=to, channel=channel, request_options=request_options + ) + return _response.data + + async def resend( + self, + session_id: str, + *, + channel: TwoFactorVerificationResendRequestChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> ResendVerificationResponse: + """ + Resends the OTP for the verification identified by `session_id` over the specified channel. Previously sent codes are invalidated. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + channel : TwoFactorVerificationResendRequestChannel + Channel used to resend the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + ResendVerificationResponse + Returns the resend result, including the channel used. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.verification.resend( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + channel="sms", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.resend(session_id, channel=channel, request_options=request_options) + return _response.data + + async def check( + self, session_id: str, *, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> CheckVerificationResponse: + """ + Validates the OTP submitted by the end user against the verification identified by `session_id`. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + code : str + The code entered by an end user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CheckVerificationResponse + Returns the validation result in `is_valid`. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.verification.check( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + code="123456", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.check(session_id, code=code, request_options=request_options) + return _response.data + + async def cancel( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> SuccessResponse: + """ + Cancels the 2FA verification identified by `session_id`. No further codes are sent, and previously sent codes can no longer be validated. A new verification is required to send another code. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The verification is canceled. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.two_fa.verification.cancel( + session_id="2953d4308f2e11ecb75fcdafd6d2d687", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.cancel(session_id, request_options=request_options) + return _response.data diff --git a/src/wavix/two_fa/verification/raw_client.py b/src/wavix/two_fa/verification/raw_client.py new file mode 100644 index 0000000..a30c1d7 --- /dev/null +++ b/src/wavix/two_fa/verification/raw_client.py @@ -0,0 +1,664 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...types.success_response import SuccessResponse +from .types.check_verification_response import CheckVerificationResponse +from .types.create_verification_response import CreateVerificationResponse +from .types.resend_verification_response import ResendVerificationResponse +from .types.two_factor_verification_resend_request_channel import TwoFactorVerificationResendRequestChannel +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawVerificationClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, *, service_id: str, to: str, channel: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CreateVerificationResponse]: + """ + Creates a 2FA verification and sends a one-time password (OTP) to the destination phone number over the selected channel. Requires a 2FA service configured in the Wavix portal; the service is reused to generate and validate OTPs. + + The verification proceeds through three steps: + 1. Create a verification to generate and send an OTP. + 2. Resend the OTP on the same verification if needed. + 3. Validate the OTP through the check endpoint. + + Parameters + ---------- + service_id : str + Unique Wavix 2FA Service ID. Available on the Wavix portal. + + to : str + End user's phone number to which the verification code will be sent. The phone number must be in E.164 format. + + channel : str + Channel used to deliver the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CreateVerificationResponse] + Returns the created 2FA verification. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/two-fa/verification", + method="POST", + json={ + "service_id": service_id, + "to": to, + "channel": channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateVerificationResponse, + parse_obj_as( + type_=CreateVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def resend( + self, + session_id: str, + *, + channel: TwoFactorVerificationResendRequestChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[ResendVerificationResponse]: + """ + Resends the OTP for the verification identified by `session_id` over the specified channel. Previously sent codes are invalidated. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + channel : TwoFactorVerificationResendRequestChannel + Channel used to resend the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[ResendVerificationResponse] + Returns the resend result, including the channel used. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}", + method="POST", + json={ + "channel": channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ResendVerificationResponse, + parse_obj_as( + type_=ResendVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def check( + self, session_id: str, *, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[CheckVerificationResponse]: + """ + Validates the OTP submitted by the end user against the verification identified by `session_id`. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + code : str + The code entered by an end user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[CheckVerificationResponse] + Returns the validation result in `is_valid`. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}/check", + method="POST", + json={ + "code": code, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CheckVerificationResponse, + parse_obj_as( + type_=CheckVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def cancel( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Cancels the 2FA verification identified by `session_id`. No further codes are sent, and previously sent codes can no longer be validated. A new verification is required to send another code. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The verification is canceled. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}/cancel", + method="PATCH", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawVerificationClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, *, service_id: str, to: str, channel: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CreateVerificationResponse]: + """ + Creates a 2FA verification and sends a one-time password (OTP) to the destination phone number over the selected channel. Requires a 2FA service configured in the Wavix portal; the service is reused to generate and validate OTPs. + + The verification proceeds through three steps: + 1. Create a verification to generate and send an OTP. + 2. Resend the OTP on the same verification if needed. + 3. Validate the OTP through the check endpoint. + + Parameters + ---------- + service_id : str + Unique Wavix 2FA Service ID. Available on the Wavix portal. + + to : str + End user's phone number to which the verification code will be sent. The phone number must be in E.164 format. + + channel : str + Channel used to deliver the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CreateVerificationResponse] + Returns the created 2FA verification. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/two-fa/verification", + method="POST", + json={ + "service_id": service_id, + "to": to, + "channel": channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CreateVerificationResponse, + parse_obj_as( + type_=CreateVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def resend( + self, + session_id: str, + *, + channel: TwoFactorVerificationResendRequestChannel, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[ResendVerificationResponse]: + """ + Resends the OTP for the verification identified by `session_id` over the specified channel. Previously sent codes are invalidated. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + channel : TwoFactorVerificationResendRequestChannel + Channel used to resend the verification code. One of `sms` (sent as a text message) or `voice` (read aloud over a phone call). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[ResendVerificationResponse] + Returns the resend result, including the channel used. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}", + method="POST", + json={ + "channel": channel, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + ResendVerificationResponse, + parse_obj_as( + type_=ResendVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def check( + self, session_id: str, *, code: str, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[CheckVerificationResponse]: + """ + Validates the OTP submitted by the end user against the verification identified by `session_id`. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + code : str + The code entered by an end user + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[CheckVerificationResponse] + Returns the validation result in `is_valid`. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}/check", + method="POST", + json={ + "code": code, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + CheckVerificationResponse, + parse_obj_as( + type_=CheckVerificationResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def cancel( + self, session_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Cancels the 2FA verification identified by `session_id`. No further codes are sent, and previously sent codes can no longer be validated. A new verification is required to send another code. + + Parameters + ---------- + session_id : str + The unique ID of the 2FA verification session. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The verification is canceled. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/two-fa/verification/{encode_path_param(session_id)}/cancel", + method="PATCH", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/two_fa/verification/types/__init__.py b/src/wavix/two_fa/verification/types/__init__.py new file mode 100644 index 0000000..8f28453 --- /dev/null +++ b/src/wavix/two_fa/verification/types/__init__.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .check_verification_response import CheckVerificationResponse + from .create_verification_response import CreateVerificationResponse + from .resend_verification_response import ResendVerificationResponse + from .two_factor_verification_resend_request_channel import TwoFactorVerificationResendRequestChannel +_dynamic_imports: typing.Dict[str, str] = { + "CheckVerificationResponse": ".check_verification_response", + "CreateVerificationResponse": ".create_verification_response", + "ResendVerificationResponse": ".resend_verification_response", + "TwoFactorVerificationResendRequestChannel": ".two_factor_verification_resend_request_channel", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CheckVerificationResponse", + "CreateVerificationResponse", + "ResendVerificationResponse", + "TwoFactorVerificationResendRequestChannel", +] diff --git a/src/wavix/two_fa/verification/types/check_verification_response.py b/src/wavix/two_fa/verification/types/check_verification_response.py new file mode 100644 index 0000000..50e198e --- /dev/null +++ b/src/wavix/two_fa/verification/types/check_verification_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CheckVerificationResponse(UniversalBaseModel): + is_valid: bool = pydantic.Field() + """ + Indicates whether the entered code is valid + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/two_fa/verification/types/create_verification_response.py b/src/wavix/two_fa/verification/types/create_verification_response.py new file mode 100644 index 0000000..9b688c6 --- /dev/null +++ b/src/wavix/two_fa/verification/types/create_verification_response.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ....types.phone_lookup_details import PhoneLookupDetails + + +class CreateVerificationResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the 2FA Verification was successfully created + """ + + service_id: str = pydantic.Field() + """ + Unique identifier of the Wavix 2FA Service + """ + + session_url: str = pydantic.Field() + """ + Automatically generated 2FA Verification URL. The URL can be used to resend or validate the OTP. + """ + + session_id: str = pydantic.Field() + """ + Unique identifier of the Wavix 2FA Verification + """ + + destination: str = pydantic.Field() + """ + The end user's phone number. + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the 2FA Verification is created + """ + + number_lookup: PhoneLookupDetails + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/two_fa/verification/types/resend_verification_response.py b/src/wavix/two_fa/verification/types/resend_verification_response.py new file mode 100644 index 0000000..d01b43c --- /dev/null +++ b/src/wavix/two_fa/verification/types/resend_verification_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ResendVerificationResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the verification code was successfully sent + """ + + channel: str = pydantic.Field() + """ + Indicates whether the code was sent via an SMS or a voice call + """ + + destination: str = pydantic.Field() + """ + The destination phone number the code was sent to + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the code was sent + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/two_fa/verification/types/two_factor_verification_resend_request_channel.py b/src/wavix/two_fa/verification/types/two_factor_verification_resend_request_channel.py new file mode 100644 index 0000000..3119455 --- /dev/null +++ b/src/wavix/two_fa/verification/types/two_factor_verification_resend_request_channel.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TwoFactorVerificationResendRequestChannel = typing.Union[typing.Literal["sms", "voice"], typing.Any] diff --git a/src/wavix/types/__init__.py b/src/wavix/types/__init__.py new file mode 100644 index 0000000..0e83249 --- /dev/null +++ b/src/wavix/types/__init__.py @@ -0,0 +1,637 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .account_error_response import AccountErrorResponse + from .account_limits import AccountLimits + from .allowed_i_ps import AllowedIPs + from .allowed_i_ps_item import AllowedIPsItem + from .api_key import ApiKey + from .api_key_calls_scope_permission import ApiKeyCallsScopePermission + from .api_key_calls_scope_permission_allow import ApiKeyCallsScopePermissionAllow + from .api_key_scope_permission import ApiKeyScopePermission + from .api_key_scope_permission_allow import ApiKeyScopePermissionAllow + from .available_number import AvailableNumber + from .available_number_list_response import AvailableNumberListResponse + from .bad_request_error_body import BadRequestErrorBody + from .billing_transaction_list_response import BillingTransactionListResponse + from .brand_status_updated_webhook import BrandStatusUpdatedWebhook + from .brand_status_updated_webhook_status import BrandStatusUpdatedWebhookStatus + from .call import Call + from .call_control_validation_error_response import CallControlValidationErrorResponse + from .call_create_response import CallCreateResponse + from .call_create_response_event_type import CallCreateResponseEventType + from .call_direction import CallDirection + from .call_disposition import CallDisposition + from .call_event_type import CallEventType + from .call_list_response import CallListResponse + from .call_recording_list_response import CallRecordingListResponse + from .call_response import CallResponse + from .call_status_webhook import CallStatusWebhook + from .call_status_webhook_direction import CallStatusWebhookDirection + from .call_stream_channel import CallStreamChannel + from .call_stream_response import CallStreamResponse + from .call_stream_type import CallStreamType + from .call_webhook import CallWebhook + from .call_webhook_event_type import CallWebhookEventType + from .call_webhook_list_response import CallWebhookListResponse + from .call_webhook_list_response_item import CallWebhookListResponseItem + from .call_webhook_list_response_item_event_type import CallWebhookListResponseItemEventType + from .campaign_status_updated_webhook import CampaignStatusUpdatedWebhook + from .campaign_status_updated_webhook_status import CampaignStatusUpdatedWebhookStatus + from .cart_response import CartResponse + from .cdr import Cdr + from .cdr_list_response import CdrListResponse + from .cdr_response import CdrResponse + from .cdr_transcription_completed_webhook import CdrTranscriptionCompletedWebhook + from .cdr_transcription_completed_webhook_status import CdrTranscriptionCompletedWebhookStatus + from .cdr_transcription_response import CdrTranscriptionResponse + from .cdr_transcription_search_response import CdrTranscriptionSearchResponse + from .cdr_with_transcription import CdrWithTranscription + from .city import City + from .city_list_response import CityListResponse + from .country import Country + from .country_has_no_regions_error_response import CountryHasNoRegionsErrorResponse + from .country_list_response import CountryListResponse + from .document_type import DocumentType + from .document_type_id import DocumentTypeId + from .file_transcript_response import FileTranscriptResponse + from .file_transcript_turn import FileTranscriptTurn + from .file_transcription_completed_webhook import FileTranscriptionCompletedWebhook + from .file_transcription_response import FileTranscriptionResponse + from .file_transcription_response_language import FileTranscriptionResponseLanguage + from .file_transcription_response_status import FileTranscriptionResponseStatus + from .financial_transaction import FinancialTransaction + from .forbidden_error_response import ForbiddenErrorResponse + from .inbound_call_destination import InboundCallDestination + from .inbound_call_transport import InboundCallTransport + from .inbound_message import InboundMessage + from .invalid_recording import InvalidRecording + from .invoice import Invoice + from .invoice_list_response import InvoiceListResponse + from .list_brand_evidence_response import ListBrandEvidenceResponse + from .list_sessions_response_item import ListSessionsResponseItem + from .message import Message + from .message_body import MessageBody + from .message_create_request import MessageCreateRequest + from .message_delivery_status import MessageDeliveryStatus + from .message_list_response import MessageListResponse + from .message_response import MessageResponse + from .messages_delivery_report import MessagesDeliveryReport + from .not_found_error_body import NotFoundErrorBody + from .not_found_error_response import NotFoundErrorResponse + from .number import Number + from .number_destination import NumberDestination + from .number_document import NumberDocument + from .number_list_response import NumberListResponse + from .number_status_updated_webhook import NumberStatusUpdatedWebhook + from .number_status_updated_webhook_status import NumberStatusUpdatedWebhookStatus + from .number_validator_create_bulk_response import NumberValidatorCreateBulkResponse + from .number_validator_create_bulk_response_request_id import NumberValidatorCreateBulkResponseRequestId + from .on_call_event_payload import OnCallEventPayload + from .on_call_event_payload_type import OnCallEventPayloadType + from .opt_out import OptOut + from .opt_out_item import OptOutItem + from .opt_outs_list_response import OptOutsListResponse + from .pagination import Pagination + from .phone_lookup_details import PhoneLookupDetails + from .phone_number_validation_type import PhoneNumberValidationType + from .phone_validation_batch_response import PhoneValidationBatchResponse + from .phone_validation_batch_result_response import PhoneValidationBatchResultResponse + from .phone_validation_response import PhoneValidationResponse + from .phone_validation_result_response import PhoneValidationResultResponse + from .profile_config_response import ProfileConfigResponse + from .profile_response import ProfileResponse + from .profile_response_company_info import ProfileResponseCompanyInfo + from .profile_response_company_info_country import ProfileResponseCompanyInfoCountry + from .profile_response_company_info_industry import ProfileResponseCompanyInfoIndustry + from .profile_response_default_destinations_item import ProfileResponseDefaultDestinationsItem + from .record_not_found_error_response import RecordNotFoundErrorResponse + from .recording import Recording + from .recording_deleted_error_response import RecordingDeletedErrorResponse + from .region import Region + from .region_list_response import RegionListResponse + from .send_messages_response import SendMessagesResponse + from .sender_id import SenderId + from .sender_id_details import SenderIdDetails + from .sender_id_list_response import SenderIdListResponse + from .sender_id_response import SenderIdResponse + from .sender_id_response_type import SenderIdResponseType + from .sender_id_type import SenderIdType + from .short_link_metrics_item import ShortLinkMetricsItem + from .short_link_metrics_response import ShortLinkMetricsResponse + from .short_link_response import ShortLinkResponse + from .sip_trunk_create_request import SipTrunkCreateRequest + from .sip_trunk_create_request_allowed_ips_item import SipTrunkCreateRequestAllowedIpsItem + from .sip_trunk_create_request_host_request import SipTrunkCreateRequestHostRequest + from .sip_trunk_list_response import SipTrunkListResponse + from .sip_trunk_response import SipTrunkResponse + from .sip_trunk_summary import SipTrunkSummary + from .sip_trunk_summary_host_request import SipTrunkSummaryHostRequest + from .sub_accounts_list_response import SubAccountsListResponse + from .sub_accounts_transactions_list_response import SubAccountsTransactionsListResponse + from .sub_accounts_transactions_list_response_transactions_item import ( + SubAccountsTransactionsListResponseTransactionsItem, + ) + from .sub_organization_response import SubOrganizationResponse + from .sub_organization_response_default_destinations import SubOrganizationResponseDefaultDestinations + from .sub_organization_response_status import SubOrganizationResponseStatus + from .submit_file_transcription_response import SubmitFileTranscriptionResponse + from .success_response import SuccessResponse + from .tcr_error_message import TcrErrorMessage + from .tcr_feedback import TcrFeedback + from .tcr_feedback_category import TcrFeedbackCategory + from .ten_dlc_brand import TenDlcBrand + from .ten_dlc_brand_appeal import TenDlcBrandAppeal + from .ten_dlc_brand_appeal_create_request import TenDlcBrandAppealCreateRequest + from .ten_dlc_brand_appeal_outcome import TenDlcBrandAppealOutcome + from .ten_dlc_brand_appeal_outcome_feedback import TenDlcBrandAppealOutcomeFeedback + from .ten_dlc_brand_appeal_outcome_vetting_status import TenDlcBrandAppealOutcomeVettingStatus + from .ten_dlc_brand_create_request import TenDlcBrandCreateRequest + from .ten_dlc_brand_create_request_entity_type import TenDlcBrandCreateRequestEntityType + from .ten_dlc_brand_create_request_stock_exchange import TenDlcBrandCreateRequestStockExchange + from .ten_dlc_brand_create_request_stock_exchange_entity_type import TenDlcBrandCreateRequestStockExchangeEntityType + from .ten_dlc_brand_create_request_vertical import TenDlcBrandCreateRequestVertical + from .ten_dlc_brand_create_request_zero import TenDlcBrandCreateRequestZero + from .ten_dlc_brand_create_request_zero_entity_type import TenDlcBrandCreateRequestZeroEntityType + from .ten_dlc_brand_entity_type import TenDlcBrandEntityType + from .ten_dlc_brand_evidence import TenDlcBrandEvidence + from .ten_dlc_brand_identity_verification_status import TenDlcBrandIdentityVerificationStatus + from .ten_dlc_brand_list_response import TenDlcBrandListResponse + from .ten_dlc_brand_list_response_pagination import TenDlcBrandListResponsePagination + from .ten_dlc_brand_qualification_result import TenDlcBrandQualificationResult + from .ten_dlc_brand_status import TenDlcBrandStatus + from .ten_dlc_brand_vetting import TenDlcBrandVetting + from .ten_dlc_brand_vetting_appeal import TenDlcBrandVettingAppeal + from .ten_dlc_brand_vetting_appeal_appeal_outcome import TenDlcBrandVettingAppealAppealOutcome + from .ten_dlc_brand_vetting_appeal_appeal_outcome_feedback import TenDlcBrandVettingAppealAppealOutcomeFeedback + from .ten_dlc_brand_vetting_appeal_outcome import TenDlcBrandVettingAppealOutcome + from .ten_dlc_brand_vetting_appeal_outcome_feedback import TenDlcBrandVettingAppealOutcomeFeedback + from .ten_dlc_brand_vetting_appeal_outcome_reason import TenDlcBrandVettingAppealOutcomeReason + from .ten_dlc_campaign import TenDlcCampaign + from .ten_dlc_campaign_list_response import TenDlcCampaignListResponse + from .ten_dlc_campaign_list_response_pagination import TenDlcCampaignListResponsePagination + from .ten_dlc_campaign_nudge_request import TenDlcCampaignNudgeRequest + from .ten_dlc_campaign_number import TenDlcCampaignNumber + from .ten_dlc_campaign_number_list_response import TenDlcCampaignNumberListResponse + from .ten_dlc_event_subscription import TenDlcEventSubscription + from .ten_dlcmno_metadata import TenDlcmnoMetadata + from .transaction_status import TransactionStatus + from .transaction_type import TransactionType + from .transcript_turn import TranscriptTurn + from .transcription_filter import TranscriptionFilter + from .transcription_filter_agent import TranscriptionFilterAgent + from .transcription_filter_any import TranscriptionFilterAny + from .transcription_filter_client import TranscriptionFilterClient + from .transcription_language import TranscriptionLanguage + from .transcription_reference import TranscriptionReference + from .transcription_status import TranscriptionStatus + from .tts_language import TtsLanguage + from .tts_voice_id import TtsVoiceId + from .two_factor_verification_check_response import TwoFactorVerificationCheckResponse + from .two_factor_verification_event import TwoFactorVerificationEvent + from .two_factor_verification_resend_response import TwoFactorVerificationResendResponse + from .two_factor_verification_response import TwoFactorVerificationResponse + from .unauthorized_error_response import UnauthorizedErrorResponse + from .unprocessable_entity_error_body import UnprocessableEntityErrorBody + from .validation_error_response import ValidationErrorResponse + from .voice_campaign_create_request import VoiceCampaignCreateRequest + from .voice_campaign_response import VoiceCampaignResponse + from .voice_campaigns_create_response import VoiceCampaignsCreateResponse + from .voice_campaigns_create_response_voice_campaign import VoiceCampaignsCreateResponseVoiceCampaign + from .voice_campaigns_get_response import VoiceCampaignsGetResponse + from .voice_campaigns_get_response_voice_campaign import VoiceCampaignsGetResponseVoiceCampaign + from .web_rtc_token import WebRtcToken + from .web_rtc_token_response import WebRtcTokenResponse + from .web_rtc_tokens_list_response import WebRtcTokensListResponse +_dynamic_imports: typing.Dict[str, str] = { + "AccountErrorResponse": ".account_error_response", + "AccountLimits": ".account_limits", + "AllowedIPs": ".allowed_i_ps", + "AllowedIPsItem": ".allowed_i_ps_item", + "ApiKey": ".api_key", + "ApiKeyCallsScopePermission": ".api_key_calls_scope_permission", + "ApiKeyCallsScopePermissionAllow": ".api_key_calls_scope_permission_allow", + "ApiKeyScopePermission": ".api_key_scope_permission", + "ApiKeyScopePermissionAllow": ".api_key_scope_permission_allow", + "AvailableNumber": ".available_number", + "AvailableNumberListResponse": ".available_number_list_response", + "BadRequestErrorBody": ".bad_request_error_body", + "BillingTransactionListResponse": ".billing_transaction_list_response", + "BrandStatusUpdatedWebhook": ".brand_status_updated_webhook", + "BrandStatusUpdatedWebhookStatus": ".brand_status_updated_webhook_status", + "Call": ".call", + "CallControlValidationErrorResponse": ".call_control_validation_error_response", + "CallCreateResponse": ".call_create_response", + "CallCreateResponseEventType": ".call_create_response_event_type", + "CallDirection": ".call_direction", + "CallDisposition": ".call_disposition", + "CallEventType": ".call_event_type", + "CallListResponse": ".call_list_response", + "CallRecordingListResponse": ".call_recording_list_response", + "CallResponse": ".call_response", + "CallStatusWebhook": ".call_status_webhook", + "CallStatusWebhookDirection": ".call_status_webhook_direction", + "CallStreamChannel": ".call_stream_channel", + "CallStreamResponse": ".call_stream_response", + "CallStreamType": ".call_stream_type", + "CallWebhook": ".call_webhook", + "CallWebhookEventType": ".call_webhook_event_type", + "CallWebhookListResponse": ".call_webhook_list_response", + "CallWebhookListResponseItem": ".call_webhook_list_response_item", + "CallWebhookListResponseItemEventType": ".call_webhook_list_response_item_event_type", + "CampaignStatusUpdatedWebhook": ".campaign_status_updated_webhook", + "CampaignStatusUpdatedWebhookStatus": ".campaign_status_updated_webhook_status", + "CartResponse": ".cart_response", + "Cdr": ".cdr", + "CdrListResponse": ".cdr_list_response", + "CdrResponse": ".cdr_response", + "CdrTranscriptionCompletedWebhook": ".cdr_transcription_completed_webhook", + "CdrTranscriptionCompletedWebhookStatus": ".cdr_transcription_completed_webhook_status", + "CdrTranscriptionResponse": ".cdr_transcription_response", + "CdrTranscriptionSearchResponse": ".cdr_transcription_search_response", + "CdrWithTranscription": ".cdr_with_transcription", + "City": ".city", + "CityListResponse": ".city_list_response", + "Country": ".country", + "CountryHasNoRegionsErrorResponse": ".country_has_no_regions_error_response", + "CountryListResponse": ".country_list_response", + "DocumentType": ".document_type", + "DocumentTypeId": ".document_type_id", + "FileTranscriptResponse": ".file_transcript_response", + "FileTranscriptTurn": ".file_transcript_turn", + "FileTranscriptionCompletedWebhook": ".file_transcription_completed_webhook", + "FileTranscriptionResponse": ".file_transcription_response", + "FileTranscriptionResponseLanguage": ".file_transcription_response_language", + "FileTranscriptionResponseStatus": ".file_transcription_response_status", + "FinancialTransaction": ".financial_transaction", + "ForbiddenErrorResponse": ".forbidden_error_response", + "InboundCallDestination": ".inbound_call_destination", + "InboundCallTransport": ".inbound_call_transport", + "InboundMessage": ".inbound_message", + "InvalidRecording": ".invalid_recording", + "Invoice": ".invoice", + "InvoiceListResponse": ".invoice_list_response", + "ListBrandEvidenceResponse": ".list_brand_evidence_response", + "ListSessionsResponseItem": ".list_sessions_response_item", + "Message": ".message", + "MessageBody": ".message_body", + "MessageCreateRequest": ".message_create_request", + "MessageDeliveryStatus": ".message_delivery_status", + "MessageListResponse": ".message_list_response", + "MessageResponse": ".message_response", + "MessagesDeliveryReport": ".messages_delivery_report", + "NotFoundErrorBody": ".not_found_error_body", + "NotFoundErrorResponse": ".not_found_error_response", + "Number": ".number", + "NumberDestination": ".number_destination", + "NumberDocument": ".number_document", + "NumberListResponse": ".number_list_response", + "NumberStatusUpdatedWebhook": ".number_status_updated_webhook", + "NumberStatusUpdatedWebhookStatus": ".number_status_updated_webhook_status", + "NumberValidatorCreateBulkResponse": ".number_validator_create_bulk_response", + "NumberValidatorCreateBulkResponseRequestId": ".number_validator_create_bulk_response_request_id", + "OnCallEventPayload": ".on_call_event_payload", + "OnCallEventPayloadType": ".on_call_event_payload_type", + "OptOut": ".opt_out", + "OptOutItem": ".opt_out_item", + "OptOutsListResponse": ".opt_outs_list_response", + "Pagination": ".pagination", + "PhoneLookupDetails": ".phone_lookup_details", + "PhoneNumberValidationType": ".phone_number_validation_type", + "PhoneValidationBatchResponse": ".phone_validation_batch_response", + "PhoneValidationBatchResultResponse": ".phone_validation_batch_result_response", + "PhoneValidationResponse": ".phone_validation_response", + "PhoneValidationResultResponse": ".phone_validation_result_response", + "ProfileConfigResponse": ".profile_config_response", + "ProfileResponse": ".profile_response", + "ProfileResponseCompanyInfo": ".profile_response_company_info", + "ProfileResponseCompanyInfoCountry": ".profile_response_company_info_country", + "ProfileResponseCompanyInfoIndustry": ".profile_response_company_info_industry", + "ProfileResponseDefaultDestinationsItem": ".profile_response_default_destinations_item", + "RecordNotFoundErrorResponse": ".record_not_found_error_response", + "Recording": ".recording", + "RecordingDeletedErrorResponse": ".recording_deleted_error_response", + "Region": ".region", + "RegionListResponse": ".region_list_response", + "SendMessagesResponse": ".send_messages_response", + "SenderId": ".sender_id", + "SenderIdDetails": ".sender_id_details", + "SenderIdListResponse": ".sender_id_list_response", + "SenderIdResponse": ".sender_id_response", + "SenderIdResponseType": ".sender_id_response_type", + "SenderIdType": ".sender_id_type", + "ShortLinkMetricsItem": ".short_link_metrics_item", + "ShortLinkMetricsResponse": ".short_link_metrics_response", + "ShortLinkResponse": ".short_link_response", + "SipTrunkCreateRequest": ".sip_trunk_create_request", + "SipTrunkCreateRequestAllowedIpsItem": ".sip_trunk_create_request_allowed_ips_item", + "SipTrunkCreateRequestHostRequest": ".sip_trunk_create_request_host_request", + "SipTrunkListResponse": ".sip_trunk_list_response", + "SipTrunkResponse": ".sip_trunk_response", + "SipTrunkSummary": ".sip_trunk_summary", + "SipTrunkSummaryHostRequest": ".sip_trunk_summary_host_request", + "SubAccountsListResponse": ".sub_accounts_list_response", + "SubAccountsTransactionsListResponse": ".sub_accounts_transactions_list_response", + "SubAccountsTransactionsListResponseTransactionsItem": ".sub_accounts_transactions_list_response_transactions_item", + "SubOrganizationResponse": ".sub_organization_response", + "SubOrganizationResponseDefaultDestinations": ".sub_organization_response_default_destinations", + "SubOrganizationResponseStatus": ".sub_organization_response_status", + "SubmitFileTranscriptionResponse": ".submit_file_transcription_response", + "SuccessResponse": ".success_response", + "TcrErrorMessage": ".tcr_error_message", + "TcrFeedback": ".tcr_feedback", + "TcrFeedbackCategory": ".tcr_feedback_category", + "TenDlcBrand": ".ten_dlc_brand", + "TenDlcBrandAppeal": ".ten_dlc_brand_appeal", + "TenDlcBrandAppealCreateRequest": ".ten_dlc_brand_appeal_create_request", + "TenDlcBrandAppealOutcome": ".ten_dlc_brand_appeal_outcome", + "TenDlcBrandAppealOutcomeFeedback": ".ten_dlc_brand_appeal_outcome_feedback", + "TenDlcBrandAppealOutcomeVettingStatus": ".ten_dlc_brand_appeal_outcome_vetting_status", + "TenDlcBrandCreateRequest": ".ten_dlc_brand_create_request", + "TenDlcBrandCreateRequestEntityType": ".ten_dlc_brand_create_request_entity_type", + "TenDlcBrandCreateRequestStockExchange": ".ten_dlc_brand_create_request_stock_exchange", + "TenDlcBrandCreateRequestStockExchangeEntityType": ".ten_dlc_brand_create_request_stock_exchange_entity_type", + "TenDlcBrandCreateRequestVertical": ".ten_dlc_brand_create_request_vertical", + "TenDlcBrandCreateRequestZero": ".ten_dlc_brand_create_request_zero", + "TenDlcBrandCreateRequestZeroEntityType": ".ten_dlc_brand_create_request_zero_entity_type", + "TenDlcBrandEntityType": ".ten_dlc_brand_entity_type", + "TenDlcBrandEvidence": ".ten_dlc_brand_evidence", + "TenDlcBrandIdentityVerificationStatus": ".ten_dlc_brand_identity_verification_status", + "TenDlcBrandListResponse": ".ten_dlc_brand_list_response", + "TenDlcBrandListResponsePagination": ".ten_dlc_brand_list_response_pagination", + "TenDlcBrandQualificationResult": ".ten_dlc_brand_qualification_result", + "TenDlcBrandStatus": ".ten_dlc_brand_status", + "TenDlcBrandVetting": ".ten_dlc_brand_vetting", + "TenDlcBrandVettingAppeal": ".ten_dlc_brand_vetting_appeal", + "TenDlcBrandVettingAppealAppealOutcome": ".ten_dlc_brand_vetting_appeal_appeal_outcome", + "TenDlcBrandVettingAppealAppealOutcomeFeedback": ".ten_dlc_brand_vetting_appeal_appeal_outcome_feedback", + "TenDlcBrandVettingAppealOutcome": ".ten_dlc_brand_vetting_appeal_outcome", + "TenDlcBrandVettingAppealOutcomeFeedback": ".ten_dlc_brand_vetting_appeal_outcome_feedback", + "TenDlcBrandVettingAppealOutcomeReason": ".ten_dlc_brand_vetting_appeal_outcome_reason", + "TenDlcCampaign": ".ten_dlc_campaign", + "TenDlcCampaignListResponse": ".ten_dlc_campaign_list_response", + "TenDlcCampaignListResponsePagination": ".ten_dlc_campaign_list_response_pagination", + "TenDlcCampaignNudgeRequest": ".ten_dlc_campaign_nudge_request", + "TenDlcCampaignNumber": ".ten_dlc_campaign_number", + "TenDlcCampaignNumberListResponse": ".ten_dlc_campaign_number_list_response", + "TenDlcEventSubscription": ".ten_dlc_event_subscription", + "TenDlcmnoMetadata": ".ten_dlcmno_metadata", + "TransactionStatus": ".transaction_status", + "TransactionType": ".transaction_type", + "TranscriptTurn": ".transcript_turn", + "TranscriptionFilter": ".transcription_filter", + "TranscriptionFilterAgent": ".transcription_filter_agent", + "TranscriptionFilterAny": ".transcription_filter_any", + "TranscriptionFilterClient": ".transcription_filter_client", + "TranscriptionLanguage": ".transcription_language", + "TranscriptionReference": ".transcription_reference", + "TranscriptionStatus": ".transcription_status", + "TtsLanguage": ".tts_language", + "TtsVoiceId": ".tts_voice_id", + "TwoFactorVerificationCheckResponse": ".two_factor_verification_check_response", + "TwoFactorVerificationEvent": ".two_factor_verification_event", + "TwoFactorVerificationResendResponse": ".two_factor_verification_resend_response", + "TwoFactorVerificationResponse": ".two_factor_verification_response", + "UnauthorizedErrorResponse": ".unauthorized_error_response", + "UnprocessableEntityErrorBody": ".unprocessable_entity_error_body", + "ValidationErrorResponse": ".validation_error_response", + "VoiceCampaignCreateRequest": ".voice_campaign_create_request", + "VoiceCampaignResponse": ".voice_campaign_response", + "VoiceCampaignsCreateResponse": ".voice_campaigns_create_response", + "VoiceCampaignsCreateResponseVoiceCampaign": ".voice_campaigns_create_response_voice_campaign", + "VoiceCampaignsGetResponse": ".voice_campaigns_get_response", + "VoiceCampaignsGetResponseVoiceCampaign": ".voice_campaigns_get_response_voice_campaign", + "WebRtcToken": ".web_rtc_token", + "WebRtcTokenResponse": ".web_rtc_token_response", + "WebRtcTokensListResponse": ".web_rtc_tokens_list_response", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "AccountErrorResponse", + "AccountLimits", + "AllowedIPs", + "AllowedIPsItem", + "ApiKey", + "ApiKeyCallsScopePermission", + "ApiKeyCallsScopePermissionAllow", + "ApiKeyScopePermission", + "ApiKeyScopePermissionAllow", + "AvailableNumber", + "AvailableNumberListResponse", + "BadRequestErrorBody", + "BillingTransactionListResponse", + "BrandStatusUpdatedWebhook", + "BrandStatusUpdatedWebhookStatus", + "Call", + "CallControlValidationErrorResponse", + "CallCreateResponse", + "CallCreateResponseEventType", + "CallDirection", + "CallDisposition", + "CallEventType", + "CallListResponse", + "CallRecordingListResponse", + "CallResponse", + "CallStatusWebhook", + "CallStatusWebhookDirection", + "CallStreamChannel", + "CallStreamResponse", + "CallStreamType", + "CallWebhook", + "CallWebhookEventType", + "CallWebhookListResponse", + "CallWebhookListResponseItem", + "CallWebhookListResponseItemEventType", + "CampaignStatusUpdatedWebhook", + "CampaignStatusUpdatedWebhookStatus", + "CartResponse", + "Cdr", + "CdrListResponse", + "CdrResponse", + "CdrTranscriptionCompletedWebhook", + "CdrTranscriptionCompletedWebhookStatus", + "CdrTranscriptionResponse", + "CdrTranscriptionSearchResponse", + "CdrWithTranscription", + "City", + "CityListResponse", + "Country", + "CountryHasNoRegionsErrorResponse", + "CountryListResponse", + "DocumentType", + "DocumentTypeId", + "FileTranscriptResponse", + "FileTranscriptTurn", + "FileTranscriptionCompletedWebhook", + "FileTranscriptionResponse", + "FileTranscriptionResponseLanguage", + "FileTranscriptionResponseStatus", + "FinancialTransaction", + "ForbiddenErrorResponse", + "InboundCallDestination", + "InboundCallTransport", + "InboundMessage", + "InvalidRecording", + "Invoice", + "InvoiceListResponse", + "ListBrandEvidenceResponse", + "ListSessionsResponseItem", + "Message", + "MessageBody", + "MessageCreateRequest", + "MessageDeliveryStatus", + "MessageListResponse", + "MessageResponse", + "MessagesDeliveryReport", + "NotFoundErrorBody", + "NotFoundErrorResponse", + "Number", + "NumberDestination", + "NumberDocument", + "NumberListResponse", + "NumberStatusUpdatedWebhook", + "NumberStatusUpdatedWebhookStatus", + "NumberValidatorCreateBulkResponse", + "NumberValidatorCreateBulkResponseRequestId", + "OnCallEventPayload", + "OnCallEventPayloadType", + "OptOut", + "OptOutItem", + "OptOutsListResponse", + "Pagination", + "PhoneLookupDetails", + "PhoneNumberValidationType", + "PhoneValidationBatchResponse", + "PhoneValidationBatchResultResponse", + "PhoneValidationResponse", + "PhoneValidationResultResponse", + "ProfileConfigResponse", + "ProfileResponse", + "ProfileResponseCompanyInfo", + "ProfileResponseCompanyInfoCountry", + "ProfileResponseCompanyInfoIndustry", + "ProfileResponseDefaultDestinationsItem", + "RecordNotFoundErrorResponse", + "Recording", + "RecordingDeletedErrorResponse", + "Region", + "RegionListResponse", + "SendMessagesResponse", + "SenderId", + "SenderIdDetails", + "SenderIdListResponse", + "SenderIdResponse", + "SenderIdResponseType", + "SenderIdType", + "ShortLinkMetricsItem", + "ShortLinkMetricsResponse", + "ShortLinkResponse", + "SipTrunkCreateRequest", + "SipTrunkCreateRequestAllowedIpsItem", + "SipTrunkCreateRequestHostRequest", + "SipTrunkListResponse", + "SipTrunkResponse", + "SipTrunkSummary", + "SipTrunkSummaryHostRequest", + "SubAccountsListResponse", + "SubAccountsTransactionsListResponse", + "SubAccountsTransactionsListResponseTransactionsItem", + "SubOrganizationResponse", + "SubOrganizationResponseDefaultDestinations", + "SubOrganizationResponseStatus", + "SubmitFileTranscriptionResponse", + "SuccessResponse", + "TcrErrorMessage", + "TcrFeedback", + "TcrFeedbackCategory", + "TenDlcBrand", + "TenDlcBrandAppeal", + "TenDlcBrandAppealCreateRequest", + "TenDlcBrandAppealOutcome", + "TenDlcBrandAppealOutcomeFeedback", + "TenDlcBrandAppealOutcomeVettingStatus", + "TenDlcBrandCreateRequest", + "TenDlcBrandCreateRequestEntityType", + "TenDlcBrandCreateRequestStockExchange", + "TenDlcBrandCreateRequestStockExchangeEntityType", + "TenDlcBrandCreateRequestVertical", + "TenDlcBrandCreateRequestZero", + "TenDlcBrandCreateRequestZeroEntityType", + "TenDlcBrandEntityType", + "TenDlcBrandEvidence", + "TenDlcBrandIdentityVerificationStatus", + "TenDlcBrandListResponse", + "TenDlcBrandListResponsePagination", + "TenDlcBrandQualificationResult", + "TenDlcBrandStatus", + "TenDlcBrandVetting", + "TenDlcBrandVettingAppeal", + "TenDlcBrandVettingAppealAppealOutcome", + "TenDlcBrandVettingAppealAppealOutcomeFeedback", + "TenDlcBrandVettingAppealOutcome", + "TenDlcBrandVettingAppealOutcomeFeedback", + "TenDlcBrandVettingAppealOutcomeReason", + "TenDlcCampaign", + "TenDlcCampaignListResponse", + "TenDlcCampaignListResponsePagination", + "TenDlcCampaignNudgeRequest", + "TenDlcCampaignNumber", + "TenDlcCampaignNumberListResponse", + "TenDlcEventSubscription", + "TenDlcmnoMetadata", + "TransactionStatus", + "TransactionType", + "TranscriptTurn", + "TranscriptionFilter", + "TranscriptionFilterAgent", + "TranscriptionFilterAny", + "TranscriptionFilterClient", + "TranscriptionLanguage", + "TranscriptionReference", + "TranscriptionStatus", + "TtsLanguage", + "TtsVoiceId", + "TwoFactorVerificationCheckResponse", + "TwoFactorVerificationEvent", + "TwoFactorVerificationResendResponse", + "TwoFactorVerificationResponse", + "UnauthorizedErrorResponse", + "UnprocessableEntityErrorBody", + "ValidationErrorResponse", + "VoiceCampaignCreateRequest", + "VoiceCampaignResponse", + "VoiceCampaignsCreateResponse", + "VoiceCampaignsCreateResponseVoiceCampaign", + "VoiceCampaignsGetResponse", + "VoiceCampaignsGetResponseVoiceCampaign", + "WebRtcToken", + "WebRtcTokenResponse", + "WebRtcTokensListResponse", +] diff --git a/src/wavix/types/account_error_response.py b/src/wavix/types/account_error_response.py new file mode 100644 index 0000000..98e797a --- /dev/null +++ b/src/wavix/types/account_error_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class AccountErrorResponse(UniversalBaseModel): + """ + The feature is disabled for the account. + """ + + success: bool = pydantic.Field() + """ + Indicates whether the request was successful + """ + + message: str = pydantic.Field() + """ + Human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/account_limits.py b/src/wavix/types/account_limits.py new file mode 100644 index 0000000..11e363c --- /dev/null +++ b/src/wavix/types/account_limits.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class AccountLimits(UniversalBaseModel): + max_call_duration: int = pydantic.Field() + """ + Maximum outbound call duration, in seconds + """ + + max_sip_channels: int = pydantic.Field() + """ + Maximum number of concurrent outbound calls. + """ + + max_call_rate: str = pydantic.Field() + """ + Maximum outbound call rate, in cents + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/allowed_i_ps.py b/src/wavix/types/allowed_i_ps.py new file mode 100644 index 0000000..9f1022e --- /dev/null +++ b/src/wavix/types/allowed_i_ps.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .allowed_i_ps_item import AllowedIPsItem + +AllowedIPs = typing.List[AllowedIPsItem] diff --git a/src/wavix/types/allowed_i_ps_item.py b/src/wavix/types/allowed_i_ps_item.py new file mode 100644 index 0000000..45440f8 --- /dev/null +++ b/src/wavix/types/allowed_i_ps_item.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class AllowedIPsItem(UniversalBaseModel): + id: int = pydantic.Field() + """ + IP address ID. + """ + + ip: str = pydantic.Field() + """ + Public static IP address. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/api_key.py b/src/wavix/types/api_key.py new file mode 100644 index 0000000..2ef7e75 --- /dev/null +++ b/src/wavix/types/api_key.py @@ -0,0 +1,138 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .api_key_calls_scope_permission import ApiKeyCallsScopePermission +from .api_key_scope_permission import ApiKeyScopePermission + + +class ApiKey(UniversalBaseModel): + """ + API key details including IP restrictions and scope permissions. + """ + + id: int = pydantic.Field() + """ + API key ID. + """ + + label: str = pydantic.Field() + """ + API key label. + """ + + value: str = pydantic.Field() + """ + API key value. + """ + + active: bool = pydantic.Field() + """ + Indicates whether the API key is active. + """ + + restricted: bool = pydantic.Field() + """ + Indicates whether IP restrictions are enabled. When enabled, + the API key works only from IP addresses in `permitted_ips`. + """ + + permitted_ips: typing.List[str] = pydantic.Field() + """ + List of permitted IP addresses. If `restricted` is false, + an empty list means no IP restrictions. If `restricted` is true, + an empty list prevents all requests. + """ + + scopes_enabled: bool = pydantic.Field() + """ + When `true`, the key is restricted to the permissions defined in the + scope fields below. When `false`, the key has full access. + """ + + last_used_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Timestamp of the most recent authenticated request made with this key. + """ + + numbers: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View, buy, release, and configure phone numbers, browse inventory, and manage the cart. + """ + + trunks: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View, create, update, and delete SIP trunks and their settings. + """ + + calls: typing.Optional[ApiKeyCallsScopePermission] = pydantic.Field(default=None) + """ + Access call records and active calls, and control live call actions such as starting, answering, ending, audio playback, DTMF, streaming, and transcription requests. + """ + + messages: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + Access message history and Sender IDs, send messages, manage opt-outs, and create or delete Sender IDs. + """ + + recordings: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + List, download, and delete call recordings. + """ + + campaigns: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View campaign analytics and Sender ID or Brand status, schedule bulk voice or SMS campaigns, register Brands, and create short links. + """ + + two_fa: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View 2FA service details and verification logs, trigger OTPs by voice or SMS, and validate verification codes. + """ + + validator: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View number validation results and trigger single or bulk validation or HLR lookup requests. + """ + + webhooks: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + List, create, and delete webhooks. + """ + + embeddable: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + Manage widget tokens, including listing, viewing, creating, updating, and deleting them. + """ + + billing: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + Access statements, balance, payment methods, usage reports, and billing settings, including payment method updates. + """ + + account: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + View and update account profile information and timezone. + """ + + subaccounts: typing.Optional[ApiKeyScopePermission] = pydantic.Field(default=None) + """ + Manage subaccounts: list and view them, create, update, and suspend them. + """ + + created_at: dt.datetime = pydantic.Field() + """ + Creation date and time in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/api_key_calls_scope_permission.py b/src/wavix/types/api_key_calls_scope_permission.py new file mode 100644 index 0000000..31a4317 --- /dev/null +++ b/src/wavix/types/api_key_calls_scope_permission.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .api_key_calls_scope_permission_allow import ApiKeyCallsScopePermissionAllow + + +class ApiKeyCallsScopePermission(UniversalBaseModel): + """ + Permission level for the `calls` scope group. + """ + + allow: typing.Optional[ApiKeyCallsScopePermissionAllow] = pydantic.Field(default=None) + """ + Permission level: + - `none`: no access + - `read`: GET requests, `POST /v1/cdrs` + - `write`: all methods (includes read) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/api_key_calls_scope_permission_allow.py b/src/wavix/types/api_key_calls_scope_permission_allow.py new file mode 100644 index 0000000..172b778 --- /dev/null +++ b/src/wavix/types/api_key_calls_scope_permission_allow.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ApiKeyCallsScopePermissionAllow = typing.Union[typing.Literal["none", "read", "write"], typing.Any] diff --git a/src/wavix/types/api_key_scope_permission.py b/src/wavix/types/api_key_scope_permission.py new file mode 100644 index 0000000..8d30164 --- /dev/null +++ b/src/wavix/types/api_key_scope_permission.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .api_key_scope_permission_allow import ApiKeyScopePermissionAllow + + +class ApiKeyScopePermission(UniversalBaseModel): + """ + Permission level for an API key scope group. + """ + + allow: typing.Optional[ApiKeyScopePermissionAllow] = pydantic.Field(default=None) + """ + Permission level: + - `none`: no access + - `read`: GET only + - `write`: all methods (includes read) + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/api_key_scope_permission_allow.py b/src/wavix/types/api_key_scope_permission_allow.py new file mode 100644 index 0000000..f6f885b --- /dev/null +++ b/src/wavix/types/api_key_scope_permission_allow.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ApiKeyScopePermissionAllow = typing.Union[typing.Literal["none", "read", "write"], typing.Any] diff --git a/src/wavix/types/available_number.py b/src/wavix/types/available_number.py new file mode 100644 index 0000000..3002d9d --- /dev/null +++ b/src/wavix/types/available_number.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class AvailableNumber(UniversalBaseModel): + """ + Phone number available for purchase. + """ + + id: int = pydantic.Field() + """ + Phone number ID. + """ + + activation_fee: str = pydantic.Field() + """ + One-time activation fee in USD. + """ + + monthly_fee: str = pydantic.Field() + """ + Monthly fee in USD. + """ + + per_min: str = pydantic.Field() + """ + Price per inbound minute in USD. + """ + + channels: str = pydantic.Field() + """ + Maximum number of concurrent inbound calls. + """ + + city: str = pydantic.Field() + """ + City where the phone number originates. + """ + + country: str = pydantic.Field() + """ + Country where the phone number originates. + """ + + country_short_name: str = pydantic.Field() + """ + Two-letter ISO country code. + """ + + cnam: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether CNAM can be activated. + """ + + free_min: int = pydantic.Field() + """ + Number of free inbound minutes. + """ + + number: str = pydantic.Field() + """ + Phone number in E.164 format. + """ + + require_docs: typing.List[int] = pydantic.Field() + """ + Documents required to activate the phone number. + """ + + sms_enabled: bool = pydantic.Field() + """ + Indicates whether the phone number can receive inbound + SMS and MMS messages. + """ + + sms_price: float = pydantic.Field() + """ + Price per inbound SMS segment in USD. + """ + + domestic_cli: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number can be used as the Caller ID for local calls. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/available_number_list_response.py b/src/wavix/types/available_number_list_response.py new file mode 100644 index 0000000..3049c49 --- /dev/null +++ b/src/wavix/types/available_number_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .available_number import AvailableNumber +from .pagination import Pagination + + +class AvailableNumberListResponse(UniversalBaseModel): + dids: typing.List[AvailableNumber] = pydantic.Field() + """ + Phone numbers available for purchase that match the search criteria. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/bad_request_error_body.py b/src/wavix/types/bad_request_error_body.py new file mode 100644 index 0000000..dab5e5b --- /dev/null +++ b/src/wavix/types/bad_request_error_body.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class BadRequestErrorBody(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/billing_transaction_list_response.py b/src/wavix/types/billing_transaction_list_response.py new file mode 100644 index 0000000..5c2c683 --- /dev/null +++ b/src/wavix/types/billing_transaction_list_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .financial_transaction import FinancialTransaction +from .pagination import Pagination + + +class BillingTransactionListResponse(UniversalBaseModel): + is_empty: bool = pydantic.Field() + """ + Indicates whether there are no transactions. + """ + + transactions: typing.List[FinancialTransaction] = pydantic.Field() + """ + List of transactions. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/brand_status_updated_webhook.py b/src/wavix/types/brand_status_updated_webhook.py new file mode 100644 index 0000000..8bb658b --- /dev/null +++ b/src/wavix/types/brand_status_updated_webhook.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .brand_status_updated_webhook_status import BrandStatusUpdatedWebhookStatus + + +class BrandStatusUpdatedWebhook(UniversalBaseModel): + brand_id: str = pydantic.Field() + """ + Unique identifier of the 10DLC Brand. + """ + + status: BrandStatusUpdatedWebhookStatus = pydantic.Field() + """ + Current verification status of the 10DLC Brand. One of `SELF_DECLARED`, `UNVERIFIED`, `VERIFIED`, `VETTED_VERIFIED`, `REVIEW`, or `SUSPENDED`. `SELF_DECLARED` means the Brand registered without external verification; `UNVERIFIED` means verification has not yet succeeded; `VERIFIED` means the Brand passed standard verification; `VETTED_VERIFIED` means the Brand also passed third-party vetting; `REVIEW` means verification is in progress; `SUSPENDED` means the Brand is blocked from sending. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/brand_status_updated_webhook_status.py b/src/wavix/types/brand_status_updated_webhook_status.py new file mode 100644 index 0000000..50de870 --- /dev/null +++ b/src/wavix/types/brand_status_updated_webhook_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +BrandStatusUpdatedWebhookStatus = typing.Union[ + typing.Literal["SELF_DECLARED", "UNVERIFIED", "VERIFIED", "VETTED_VERIFIED", "REVIEW", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/types/call.py b/src/wavix/types/call.py new file mode 100644 index 0000000..ac2d28b --- /dev/null +++ b/src/wavix/types/call.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_direction import CallDirection +from .call_event_type import CallEventType +from .on_call_event_payload import OnCallEventPayload + + +class Call(UniversalBaseModel): + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Call ID. Deprecated — use `id` instead."), + ] + id: str = pydantic.Field() + """ + Call ID. Alias of `uuid`. + """ + + direction: typing.Optional[CallDirection] = None + event_type: CallEventType = pydantic.Field() + """ + Most recent lifecycle event for the call. One of: + + - `call_setup` — the call is being initiated. + - `ringing` — the destination is ringing. + - `early_media` — early media (such as ringback) is playing before answer. + - `answered` — the destination answered the call. + - `completed` — the call ended normally. + - `busy` — the destination was busy. + - `cancelled` — the call was cancelled before answer. + - `rejected` — the destination rejected the call. + - `on_call_event` — an in-call event occurred (see `event_payload`). + """ + + event_time: dt.datetime = pydantic.Field() + """ + Date and time of the latest event in ISO 8601 format. + """ + + event_payload: typing.Optional[OnCallEventPayload] = pydantic.Field(default=None) + """ + Event-specific metadata. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Caller ID.") + ] + to: str = pydantic.Field() + """ + Destination number. + """ + + call_started: dt.datetime = pydantic.Field() + """ + Date and time when the call started in ISO 8601 format. + """ + + call_answered: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the call was answered in ISO 8601 format. + """ + + call_completed: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the call ended in ISO 8601 format. + """ + + machine_detected: bool = pydantic.Field() + """ + Indicates whether the call was answered by a machine. + """ + + tag: str = pydantic.Field() + """ + Call metadata + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_control_validation_error_response.py b/src/wavix/types/call_control_validation_error_response.py new file mode 100644 index 0000000..72db3b7 --- /dev/null +++ b/src/wavix/types/call_control_validation_error_response.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CallControlValidationErrorResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable summary of the validation failure. + """ + + errors: typing.Optional[typing.Dict[str, str]] = pydantic.Field(default=None) + """ + Field-level validation messages, keyed by the offending request + field name (snake_case). Absent when the request body could not be + parsed at all (for example, malformed JSON). + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_create_response.py b/src/wavix/types/call_create_response.py new file mode 100644 index 0000000..60243e1 --- /dev/null +++ b/src/wavix/types/call_create_response.py @@ -0,0 +1,92 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_create_response_event_type import CallCreateResponseEventType +from .call_direction import CallDirection +from .on_call_event_payload import OnCallEventPayload + + +class CallCreateResponse(UniversalBaseModel): + uuid_: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Call ID. Deprecated — use `id` instead."), + ] = None + id: typing.Optional[str] = pydantic.Field(default=None) + """ + Call ID. Alias of `uuid`. + """ + + direction: typing.Optional[CallDirection] = None + event_type: typing.Optional[CallCreateResponseEventType] = pydantic.Field(default=None) + """ + Most recent lifecycle event for the call. One of: + + - `call_setup` — the call is being initiated. + - `ringing` — the destination is ringing. + - `early_media` — early media (such as ringback) is playing before answer. + - `answered` — the destination answered the call. + - `completed` — the call ended normally. + - `busy` — the destination was busy. + - `cancelled` — the call was cancelled before answer. + - `rejected` — the destination rejected the call. + - `on_call_event` — an in-call event occurred (see `event_payload`). + """ + + event_time: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time of the latest event + """ + + event_payload: typing.Optional[OnCallEventPayload] = pydantic.Field(default=None) + """ + Event-specific data + """ + + from_: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Caller ID") + ] = None + to: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination number + """ + + call_started: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the call started + """ + + call_answered: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the call was answered + """ + + call_completed: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the call ended + """ + + machine_detected: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the call was answered by an answering machine + """ + + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Call metadata + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_create_response_event_type.py b/src/wavix/types/call_create_response_event_type.py new file mode 100644 index 0000000..15865fa --- /dev/null +++ b/src/wavix/types/call_create_response_event_type.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallCreateResponseEventType = typing.Union[ + typing.Literal[ + "call_setup", + "ringing", + "early_media", + "answered", + "completed", + "busy", + "cancelled", + "rejected", + "on_call_event", + ], + typing.Any, +] diff --git a/src/wavix/types/call_direction.py b/src/wavix/types/call_direction.py new file mode 100644 index 0000000..08ac8dc --- /dev/null +++ b/src/wavix/types/call_direction.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallDirection = typing.Union[typing.Literal["inbound", "outbound"], typing.Any] diff --git a/src/wavix/types/call_disposition.py b/src/wavix/types/call_disposition.py new file mode 100644 index 0000000..029f6dc --- /dev/null +++ b/src/wavix/types/call_disposition.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallDisposition = typing.Union[typing.Literal["answered", "noanswer", "busy", "failed", "all"], typing.Any] diff --git a/src/wavix/types/call_event_type.py b/src/wavix/types/call_event_type.py new file mode 100644 index 0000000..1c8a95b --- /dev/null +++ b/src/wavix/types/call_event_type.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallEventType = typing.Union[ + typing.Literal[ + "call_setup", + "ringing", + "early_media", + "answered", + "completed", + "busy", + "cancelled", + "rejected", + "on_call_event", + ], + typing.Any, +] diff --git a/src/wavix/types/call_list_response.py b/src/wavix/types/call_list_response.py new file mode 100644 index 0000000..61b29e5 --- /dev/null +++ b/src/wavix/types/call_list_response.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .call import Call + + +class CallListResponse(UniversalBaseModel): + calls: typing.List[Call] = pydantic.Field() + """ + List of calls + """ + + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_recording_list_response.py b/src/wavix/types/call_recording_list_response.py new file mode 100644 index 0000000..0cefc21 --- /dev/null +++ b/src/wavix/types/call_recording_list_response.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .invalid_recording import InvalidRecording +from .pagination import Pagination +from .recording import Recording + + +class CallRecordingListResponse(UniversalBaseModel): + recordings: typing.Optional[typing.List[Recording]] = pydantic.Field(default=None) + """ + Call recordings that match the request. + """ + + invalid: typing.Optional[InvalidRecording] = None + pagination: typing.Optional[Pagination] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_response.py b/src/wavix/types/call_response.py new file mode 100644 index 0000000..b705a0a --- /dev/null +++ b/src/wavix/types/call_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .call import Call + + +class CallResponse(UniversalBaseModel): + call: Call + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_status_webhook.py b/src/wavix/types/call_status_webhook.py new file mode 100644 index 0000000..81ba421 --- /dev/null +++ b/src/wavix/types/call_status_webhook.py @@ -0,0 +1,70 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_disposition import CallDisposition +from .call_status_webhook_direction import CallStatusWebhookDirection + + +class CallStatusWebhook(UniversalBaseModel): + """ + Post-call webhook payload + """ + + direction: CallStatusWebhookDirection = pydantic.Field() + """ + Direction of the call. One of `inbound` (call received by the account) or `outbound` (call placed by the account). + """ + + uuid_: typing_extensions.Annotated[ + str, FieldMetadata(alias="uuid"), pydantic.Field(alias="uuid", description="Call ID") + ] + destination: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination of the call. For outbound calls, it contains the country name and, optionally, a mobile carrier or city name. For inbound calls, the destination includes the user-defined SIP trunk name, SIP URI, or PSTN number that the call is forwarded to. + """ + + duration: int = pydantic.Field() + """ + Duration of the call, in seconds + """ + + charge: str = pydantic.Field() + """ + Total charge for the call, in USD + """ + + date: dt.datetime = pydantic.Field() + """ + Date and time of the call + """ + + disposition: CallDisposition + from_: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="ANI/From attribute of the call"), + ] = None + to: typing.Optional[str] = pydantic.Field(default=None) + """ + DNIS/To attribute of the call + """ + + per_minute: str = pydantic.Field() + """ + Price per minute, in USD + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_status_webhook_direction.py b/src/wavix/types/call_status_webhook_direction.py new file mode 100644 index 0000000..b270c6a --- /dev/null +++ b/src/wavix/types/call_status_webhook_direction.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallStatusWebhookDirection = typing.Union[typing.Literal["inbound", "outbound"], typing.Any] diff --git a/src/wavix/types/call_stream_channel.py b/src/wavix/types/call_stream_channel.py new file mode 100644 index 0000000..296d11e --- /dev/null +++ b/src/wavix/types/call_stream_channel.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallStreamChannel = typing.Union[typing.Literal["inbound", "outbound", "both"], typing.Any] diff --git a/src/wavix/types/call_stream_response.py b/src/wavix/types/call_stream_response.py new file mode 100644 index 0000000..59ddc78 --- /dev/null +++ b/src/wavix/types/call_stream_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CallStreamResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates that the request was successful + """ + + stream_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Stream ID + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_stream_type.py b/src/wavix/types/call_stream_type.py new file mode 100644 index 0000000..bc55e82 --- /dev/null +++ b/src/wavix/types/call_stream_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallStreamType = typing.Union[typing.Literal["oneway", "twoway"], typing.Any] diff --git a/src/wavix/types/call_webhook.py b/src/wavix/types/call_webhook.py new file mode 100644 index 0000000..3e457f1 --- /dev/null +++ b/src/wavix/types/call_webhook.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .call_webhook_event_type import CallWebhookEventType + + +class CallWebhook(UniversalBaseModel): + """ + A call webhook response + """ + + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + event_type: CallWebhookEventType = pydantic.Field() + """ + Type of call event the webhook subscribes to. One of `post-call` (delivered once after the call ends) or `on-call` (delivered for in-call events while the call is active). + """ + + url: str = pydantic.Field() + """ + Webhook URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_webhook_event_type.py b/src/wavix/types/call_webhook_event_type.py new file mode 100644 index 0000000..2455c00 --- /dev/null +++ b/src/wavix/types/call_webhook_event_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallWebhookEventType = typing.Union[typing.Literal["post-call", "on-call"], typing.Any] diff --git a/src/wavix/types/call_webhook_list_response.py b/src/wavix/types/call_webhook_list_response.py new file mode 100644 index 0000000..3a48263 --- /dev/null +++ b/src/wavix/types/call_webhook_list_response.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .call_webhook_list_response_item import CallWebhookListResponseItem + +CallWebhookListResponse = typing.List[CallWebhookListResponseItem] +""" +List of call webhooks configured for the phone number. +""" diff --git a/src/wavix/types/call_webhook_list_response_item.py b/src/wavix/types/call_webhook_list_response_item.py new file mode 100644 index 0000000..1fbd36b --- /dev/null +++ b/src/wavix/types/call_webhook_list_response_item.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .call_webhook_list_response_item_event_type import CallWebhookListResponseItemEventType + + +class CallWebhookListResponseItem(UniversalBaseModel): + event_type: CallWebhookListResponseItemEventType = pydantic.Field() + """ + Type of call event the webhook subscribes to. One of `post-call` (delivered once after the call ends) or `on-call` (delivered for in-call events while the call is active). + """ + + url: str = pydantic.Field() + """ + Webhook URL + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/call_webhook_list_response_item_event_type.py b/src/wavix/types/call_webhook_list_response_item_event_type.py new file mode 100644 index 0000000..68c62e7 --- /dev/null +++ b/src/wavix/types/call_webhook_list_response_item_event_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CallWebhookListResponseItemEventType = typing.Union[typing.Literal["post-call", "on-call"], typing.Any] diff --git a/src/wavix/types/campaign_status_updated_webhook.py b/src/wavix/types/campaign_status_updated_webhook.py new file mode 100644 index 0000000..d3eb63c --- /dev/null +++ b/src/wavix/types/campaign_status_updated_webhook.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .campaign_status_updated_webhook_status import CampaignStatusUpdatedWebhookStatus + + +class CampaignStatusUpdatedWebhook(UniversalBaseModel): + brand_id: str = pydantic.Field() + """ + Unique identifier of a 10DLC Brand + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of a 10DLC Campaign + """ + + status: CampaignStatusUpdatedWebhookStatus = pydantic.Field() + """ + Current status of the 10DLC Campaign. One of: + + - `REVIEW` — the campaign is under review. + - `APPROVED` — the campaign was approved and can send. + - `DECLINED` — the campaign was rejected during review. + - `DELETED` — the campaign was removed. + - `EXPIRED` — the campaign registration lapsed. + - `SUSPENDED` — the campaign is blocked from sending. + - `PORTED_OUT` — the campaign was migrated to another provider. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/campaign_status_updated_webhook_status.py b/src/wavix/types/campaign_status_updated_webhook_status.py new file mode 100644 index 0000000..dda0479 --- /dev/null +++ b/src/wavix/types/campaign_status_updated_webhook_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CampaignStatusUpdatedWebhookStatus = typing.Union[ + typing.Literal["REVIEW", "APPROVED", "DECLINED", "DELETED", "EXPIRED", "SUSPENDED", "PORTED_OUT"], typing.Any +] diff --git a/src/wavix/types/cart_response.py b/src/wavix/types/cart_response.py new file mode 100644 index 0000000..49ddc26 --- /dev/null +++ b/src/wavix/types/cart_response.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .available_number import AvailableNumber +from .document_type import DocumentType + + +class CartResponse(UniversalBaseModel): + dids: typing.List[AvailableNumber] = pydantic.Field() + """ + List of phone numbers in the cart. + """ + + doc_types: typing.List[DocumentType] = pydantic.Field() + """ + Document types required to activate phone numbers. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr.py b/src/wavix/types/cdr.py new file mode 100644 index 0000000..ab4f50b --- /dev/null +++ b/src/wavix/types/cdr.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_disposition import CallDisposition + + +class Cdr(UniversalBaseModel): + """ + Call detail record (CDR) for an individual call. + """ + + date: dt.datetime = pydantic.Field() + """ + Date and time of the call in ISO 8601 format. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Originating phone number (ANI).") + ] + to: str = pydantic.Field() + """ + Destination phone number (DNIS). + """ + + disposition: CallDisposition + duration: int = pydantic.Field() + """ + Call duration in seconds. + """ + + destination: str = pydantic.Field() + """ + Destination of the call. For outbound calls, + contains the country name and, optionally, a mobile carrier + or city name. For inbound calls, includes the SIP trunk name, + SIP URI, or PSTN number the call is forwarded to. + """ + + per_minute: str = pydantic.Field() + """ + Price per minute in USD. + """ + + charge: str = pydantic.Field() + """ + Total charge for the call in USD. + """ + + sip_trunk: typing.Optional[str] = pydantic.Field(default=None) + """ + System-generated SIP trunk login. For outbound calls only. + """ + + forward_fee: typing.Optional[str] = pydantic.Field(default=None) + """ + PSTN forwarding price in USD. For inbound calls only when forwarded to PSTN. + """ + + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Call ID. Deprecated — use `call_id` instead."), + ] + call_id: str = pydantic.Field() + """ + Call ID. Alias of `uuid`. + """ + + parent_uuid: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Deprecated — use `parent_call_id` instead. + """ + + parent_call_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Alias of `parent_uuid`. + """ + + answered_by: typing.Optional[str] = pydantic.Field(default=None) + """ + Who answered the call. Possible values are `human`, `machine`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_list_response.py b/src/wavix/types/cdr_list_response.py new file mode 100644 index 0000000..f3d5b8a --- /dev/null +++ b/src/wavix/types/cdr_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .cdr import Cdr +from .pagination import Pagination + + +class CdrListResponse(UniversalBaseModel): + items: typing.List[Cdr] = pydantic.Field() + """ + List of CDRs + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_response.py b/src/wavix/types/cdr_response.py new file mode 100644 index 0000000..f3a1760 --- /dev/null +++ b/src/wavix/types/cdr_response.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_disposition import CallDisposition +from .transcription_reference import TranscriptionReference + + +class CdrResponse(UniversalBaseModel): + date: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Call date and time in ISO 8601 format. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Caller ID (ANI).") + ] + to: str = pydantic.Field() + """ + Dialed number (DNIS). + """ + + disposition: CallDisposition + duration: int = pydantic.Field() + """ + Call duration in seconds. + """ + + destination: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination of the call. For outbound calls, + contains the country name and, optionally, a mobile carrier + or city name. For inbound calls, includes the SIP trunk name, + SIP URI, or PSTN number the call is forwarded to. + """ + + per_minute: typing.Optional[str] = pydantic.Field(default=None) + """ + Price per minute in USD. + """ + + recording_url: typing.Optional[str] = pydantic.Field(default=None) + """ + URL of the recorded call file. Call recording can be enabled + on a SIP trunk for outbound calls and on + a phone number for inbound calls. + """ + + charge: typing.Optional[str] = pydantic.Field(default=None) + """ + Total charge for the call in USD. + """ + + sip_trunk: typing.Optional[str] = pydantic.Field(default=None) + """ + System-generated SIP trunk login. For outbound calls only. + """ + + forward_fee: typing.Optional[str] = pydantic.Field(default=None) + """ + PSTN forwarding price in USD. For inbound calls only + when forwarded to PSTN. + """ + + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Call ID. Deprecated — use `call_id` instead."), + ] + call_id: str = pydantic.Field() + """ + Call ID. Alias of `uuid`. + """ + + parent_uuid: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Deprecated — use `parent_call_id` instead. + """ + + parent_call_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Alias of `parent_uuid`. + """ + + answered_by: typing.Optional[str] = pydantic.Field(default=None) + """ + Who answered the call. Possible values are `human`, `machine`. + """ + + transcription: typing.Optional[TranscriptionReference] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_transcription_completed_webhook.py b/src/wavix/types/cdr_transcription_completed_webhook.py new file mode 100644 index 0000000..295aa8d --- /dev/null +++ b/src/wavix/types/cdr_transcription_completed_webhook.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .cdr_transcription_completed_webhook_status import CdrTranscriptionCompletedWebhookStatus + + +class CdrTranscriptionCompletedWebhook(UniversalBaseModel): + uuid_: typing_extensions.Annotated[ + str, FieldMetadata(alias="uuid"), pydantic.Field(alias="uuid", description="Call UUID") + ] + status: CdrTranscriptionCompletedWebhookStatus = pydantic.Field() + """ + Outcome of the transcription. One of `completed` (the transcript was produced) or `failed` (the transcription could not be produced). + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_transcription_completed_webhook_status.py b/src/wavix/types/cdr_transcription_completed_webhook_status.py new file mode 100644 index 0000000..00a6685 --- /dev/null +++ b/src/wavix/types/cdr_transcription_completed_webhook_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +CdrTranscriptionCompletedWebhookStatus = typing.Union[typing.Literal["completed", "failed"], typing.Any] diff --git a/src/wavix/types/cdr_transcription_response.py b/src/wavix/types/cdr_transcription_response.py new file mode 100644 index 0000000..f72ff8a --- /dev/null +++ b/src/wavix/types/cdr_transcription_response.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .transcript_turn import TranscriptTurn +from .transcription_language import TranscriptionLanguage +from .transcription_status import TranscriptionStatus + + +class CdrTranscriptionResponse(UniversalBaseModel): + transcript: typing.Dict[str, str] = pydantic.Field() + """ + Mapping of phone numbers in the call to transcript text. + """ + + turns: typing.List[TranscriptTurn] = pydantic.Field() + """ + List of speaker turns with text and start/end times. + """ + + uuid_: typing_extensions.Annotated[ + str, FieldMetadata(alias="uuid"), pydantic.Field(alias="uuid", description="Transcription ID.") + ] + language: TranscriptionLanguage + duration: int = pydantic.Field() + """ + Call duration in seconds. + """ + + charge: str = pydantic.Field() + """ + Total charge for the transcription in USD. + """ + + status: TranscriptionStatus + transcription_date: dt.datetime = pydantic.Field() + """ + Date and time when the transcription was processed in ISO 8601 format. + """ + + call_date: dt.datetime = pydantic.Field() + """ + Date and time when the call was placed or received in ISO 8601 format. + """ + + call_uuid: str = pydantic.Field() + """ + Associated call ID. + """ + + call_score: str = pydantic.Field() + """ + Call sentiment score. Indicates negative (1.0-3.0), neutral, + or positive (4.0-5.0). + """ + + call_summary: str = pydantic.Field() + """ + One- or two-sentence call summary. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_transcription_search_response.py b/src/wavix/types/cdr_transcription_search_response.py new file mode 100644 index 0000000..26d2d29 --- /dev/null +++ b/src/wavix/types/cdr_transcription_search_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .cdr_with_transcription import CdrWithTranscription +from .pagination import Pagination + + +class CdrTranscriptionSearchResponse(UniversalBaseModel): + items: typing.List[CdrWithTranscription] = pydantic.Field() + """ + List of CDRs with transcription links. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/cdr_with_transcription.py b/src/wavix/types/cdr_with_transcription.py new file mode 100644 index 0000000..0cfb9ee --- /dev/null +++ b/src/wavix/types/cdr_with_transcription.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .call_disposition import CallDisposition +from .transcription_reference import TranscriptionReference + + +class CdrWithTranscription(UniversalBaseModel): + """ + A CDR of a single call with call transcription + """ + + answered_by: typing.Optional[str] = pydantic.Field(default=None) + """ + Who answered the call. Allowed values are `human`, `machine`. + """ + + date: dt.datetime = pydantic.Field() + """ + Date and time of the call + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="ANI/From attribute of the call") + ] + to: str = pydantic.Field() + """ + DNIS/To attribute of the call + """ + + disposition: CallDisposition + duration: int = pydantic.Field() + """ + Duration of the call, in seconds + """ + + destination: str = pydantic.Field() + """ + Destination of the call. For outbound calls, it contains the country name and, optionally, a mobile carrier or city name. For inbound calls, the destination includes the user-defined SIP trunk name, SIP URI, or PSTN number that the call is forwarded to. + """ + + per_minute: str = pydantic.Field() + """ + Price per minute, in USD + """ + + charge: str = pydantic.Field() + """ + Total charge for the call, in USD + """ + + sip_trunk: typing.Optional[str] = pydantic.Field(default=None) + """ + System-generated login of a SIP trunk. For `placed` calls only. + """ + + forward_fee: typing.Optional[str] = pydantic.Field(default=None) + """ + PSTN forwarding price, in USD. For `received` calls only when forwarded to PSTN. + """ + + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Call ID. Deprecated — use `call_id` instead."), + ] + call_id: str = pydantic.Field() + """ + Call ID. Alias of `uuid`. + """ + + parent_uuid: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Deprecated — use `parent_call_id` instead. + """ + + parent_call_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Parent call ID. Alias of `parent_uuid`. + """ + + transcription: typing.Optional[TranscriptionReference] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/city.py b/src/wavix/types/city.py new file mode 100644 index 0000000..0ba5f08 --- /dev/null +++ b/src/wavix/types/city.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class City(UniversalBaseModel): + id: int = pydantic.Field() + """ + City ID. + """ + + name: str = pydantic.Field() + """ + City name. + """ + + area_code: float = pydantic.Field() + """ + Rate center. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/city_list_response.py b/src/wavix/types/city_list_response.py new file mode 100644 index 0000000..5fdd0ed --- /dev/null +++ b/src/wavix/types/city_list_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .city import City + + +class CityListResponse(UniversalBaseModel): + cities: typing.List[City] = pydantic.Field() + """ + Cities available for the requested country or region. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/country.py b/src/wavix/types/country.py new file mode 100644 index 0000000..468b5a5 --- /dev/null +++ b/src/wavix/types/country.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Country(UniversalBaseModel): + id: int = pydantic.Field() + """ + Country ID. + """ + + name: str = pydantic.Field() + """ + Country name. + """ + + has_provinces_or_states: bool = pydantic.Field() + """ + Indicates whether the country has regions. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/country_has_no_regions_error_response.py b/src/wavix/types/country_has_no_regions_error_response.py new file mode 100644 index 0000000..c7913e9 --- /dev/null +++ b/src/wavix/types/country_has_no_regions_error_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CountryHasNoRegionsErrorResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the request succeeded. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error message. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/country_list_response.py b/src/wavix/types/country_list_response.py new file mode 100644 index 0000000..3eaf912 --- /dev/null +++ b/src/wavix/types/country_list_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .country import Country + + +class CountryListResponse(UniversalBaseModel): + countries: typing.List[Country] = pydantic.Field() + """ + Countries where phone numbers are available. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/document_type.py b/src/wavix/types/document_type.py new file mode 100644 index 0000000..98e9cdd --- /dev/null +++ b/src/wavix/types/document_type.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DocumentType(UniversalBaseModel): + id: int = pydantic.Field() + """ + Document type ID. + """ + + name: str = pydantic.Field() + """ + Document type name. + """ + + title: str = pydantic.Field() + """ + Human-readable document type name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/document_type_id.py b/src/wavix/types/document_type_id.py new file mode 100644 index 0000000..72a3282 --- /dev/null +++ b/src/wavix/types/document_type_id.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +DocumentTypeId = int +""" +Specifies the type of document required to activate the phone number. +Possible values are: `1` - Proof of identity, `2` - Proof of address, `3` - Proof of business registration. +""" diff --git a/src/wavix/types/file_transcript_response.py b/src/wavix/types/file_transcript_response.py new file mode 100644 index 0000000..ffcb1ba --- /dev/null +++ b/src/wavix/types/file_transcript_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class FileTranscriptResponse(UniversalBaseModel): + channel1: typing_extensions.Annotated[ + str, + FieldMetadata(alias="channel_1"), + pydantic.Field(alias="channel_1", description="Transcription of speaker one."), + ] + channel2: typing_extensions.Annotated[ + str, + FieldMetadata(alias="channel_2"), + pydantic.Field(alias="channel_2", description="Transcription of speaker two."), + ] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/file_transcript_turn.py b/src/wavix/types/file_transcript_turn.py new file mode 100644 index 0000000..eb4bbd0 --- /dev/null +++ b/src/wavix/types/file_transcript_turn.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FileTranscriptTurn(UniversalBaseModel): + """ + Transcription `turn` details, including speaker, timestamps, text, and sentiment. + """ + + speaker: str = pydantic.Field() + """ + Speaker identifier. + """ + + s: int = pydantic.Field() + """ + Turn start time in milliseconds from the beginning of the file. + """ + + e: int = pydantic.Field() + """ + Turn end time in milliseconds from the beginning of the file. + """ + + text: str = pydantic.Field() + """ + Transcription text attributed to the speaker. + """ + + sentiment: str = pydantic.Field() + """ + Sentiment associated with the turn. Possible values are `positive`, `neutral`, `negative`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/file_transcription_completed_webhook.py b/src/wavix/types/file_transcription_completed_webhook.py new file mode 100644 index 0000000..1032a8b --- /dev/null +++ b/src/wavix/types/file_transcription_completed_webhook.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class FileTranscriptionCompletedWebhook(UniversalBaseModel): + request_id: str = pydantic.Field() + """ + Transcription request ID + """ + + status: str = pydantic.Field() + """ + Outcome of the transcription. One of `completed` (the transcript was produced) or `failed` (the transcription could not be produced). + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + A human-readable error description, if any + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/file_transcription_response.py b/src/wavix/types/file_transcription_response.py new file mode 100644 index 0000000..db74443 --- /dev/null +++ b/src/wavix/types/file_transcription_response.py @@ -0,0 +1,77 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .file_transcript_response import FileTranscriptResponse +from .file_transcript_turn import FileTranscriptTurn +from .file_transcription_response_language import FileTranscriptionResponseLanguage +from .file_transcription_response_status import FileTranscriptionResponseStatus + + +class FileTranscriptionResponse(UniversalBaseModel): + transcript: typing.Optional[FileTranscriptResponse] = pydantic.Field(default=None) + """ + Complete transcription text attributed to each channel. + """ + + turns: typing.Optional[typing.List[FileTranscriptTurn]] = pydantic.Field(default=None) + """ + List of transcription turns, including speaker attribution, timestamps, and sentiment. + """ + + request_id: str = pydantic.Field() + """ + Transcription request ID. + """ + + language: FileTranscriptionResponseLanguage = pydantic.Field() + """ + Transcription language. + """ + + duration: typing.Optional[int] = pydantic.Field(default=None) + """ + File duration in seconds. + """ + + charge: str = pydantic.Field() + """ + Total transcription charge in USD. + """ + + status: FileTranscriptionResponseStatus = pydantic.Field() + """ + Transcription status. Possible values are `completed`, `failed`. + """ + + transcription_date: dt.datetime = pydantic.Field() + """ + Date and time of the transcription in ISO 8601 format. + """ + + transcription_score: typing.Optional[str] = pydantic.Field(default=None) + """ + Conversation sentiment score. Scores from 1.0 to 3.0 are negative; scores from 4.0 to 5.0 are positive. + """ + + transcription_summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcription summary. + """ + + original_file: str = pydantic.Field() + """ + Uploaded file URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/file_transcription_response_language.py b/src/wavix/types/file_transcription_response_language.py new file mode 100644 index 0000000..c0b790b --- /dev/null +++ b/src/wavix/types/file_transcription_response_language.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FileTranscriptionResponseLanguage = typing.Union[typing.Literal["en", "de", "es", "fr", "it"], typing.Any] diff --git a/src/wavix/types/file_transcription_response_status.py b/src/wavix/types/file_transcription_response_status.py new file mode 100644 index 0000000..004f0ce --- /dev/null +++ b/src/wavix/types/file_transcription_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +FileTranscriptionResponseStatus = typing.Union[typing.Literal["completed", "failed"], typing.Any] diff --git a/src/wavix/types/financial_transaction.py b/src/wavix/types/financial_transaction.py new file mode 100644 index 0000000..f24d98a --- /dev/null +++ b/src/wavix/types/financial_transaction.py @@ -0,0 +1,57 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .transaction_status import TransactionStatus +from .transaction_type import TransactionType + + +class FinancialTransaction(UniversalBaseModel): + """ + Financial transaction. + """ + + id: int = pydantic.Field() + """ + Transaction ID. + """ + + date: dt.datetime = pydantic.Field() + """ + Creation date and time in ISO 8601 format. + """ + + amount: float = pydantic.Field() + """ + Transaction amount. Negative values decrease balance. + """ + + balance_after: float = pydantic.Field() + """ + Account balance after the transaction. + """ + + details: str = pydantic.Field() + """ + Transaction details. + """ + + status: TransactionStatus + type: TransactionType + show_invoice: bool = pydantic.Field() + """ + Indicates whether a transaction receipt is available. Receipts + are available only for transaction types 0, 8, 9, 19, 23, and 25. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/forbidden_error_response.py b/src/wavix/types/forbidden_error_response.py new file mode 100644 index 0000000..f5bad5f --- /dev/null +++ b/src/wavix/types/forbidden_error_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ForbiddenErrorResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable description of why access is forbidden. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/inbound_call_destination.py b/src/wavix/types/inbound_call_destination.py new file mode 100644 index 0000000..634cc95 --- /dev/null +++ b/src/wavix/types/inbound_call_destination.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .inbound_call_transport import InboundCallTransport + + +class InboundCallDestination(UniversalBaseModel): + """ + Inbound call destination + """ + + id: int = pydantic.Field() + """ + Unique identifier of the inbound call destination + """ + + destination: str = pydantic.Field() + """ + The destination for inbound call routing. Can be either a SIP URI, a PSTN phone number or system-generated login of a SIP trunk on the platform. + """ + + priority: int = pydantic.Field() + """ + For phone numbers with several destinations, sets the order in which the platform routes inbound calls. The lower the value, the higher the priority. + """ + + transport: InboundCallTransport + trunk_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Unique identified of a SIP trunk on the platform. In cases when `transport:5`, otherwise, `null` + """ + + srtp: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether SRTP media encryption is enabled for this destination. + """ + + trunk_label: typing.Optional[str] = pydantic.Field(default=None) + """ + A user-defined name of a SIP trunk on the platform. In cases when `transport:5`, otherwise, `null` + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/inbound_call_transport.py b/src/wavix/types/inbound_call_transport.py new file mode 100644 index 0000000..e3f1b9b --- /dev/null +++ b/src/wavix/types/inbound_call_transport.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +InboundCallTransport = int +""" +Transport used to route the inbound call to its destination. One of `1` (SIP URI), `4` (PSTN phone number), or `5` (SIP trunk on the platform). +""" diff --git a/src/wavix/types/inbound_message.py b/src/wavix/types/inbound_message.py new file mode 100644 index 0000000..943ce1f --- /dev/null +++ b/src/wavix/types/inbound_message.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_body import MessageBody + + +class InboundMessage(UniversalBaseModel): + """ + An inbound message + """ + + message_id: str = pydantic.Field() + """ + Unique identifier of the message generated by the platform + """ + + from_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Sender ID used to send the message. Can be numeric or alphanumeric."), + ] + to: str = pydantic.Field() + """ + Phone number on the account that received the message. + """ + + message_body: MessageBody + received_at: dt.datetime = pydantic.Field() + """ + Timestamp the messages is received by Wavix + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/invalid_recording.py b/src/wavix/types/invalid_recording.py new file mode 100644 index 0000000..f6db602 --- /dev/null +++ b/src/wavix/types/invalid_recording.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class InvalidRecording(UniversalBaseModel): + """ + An invalid recording filter response + """ + + dids: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Phone number IDs that did not match any recording. + """ + + sip_trunks: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + SIP trunk IDs that did not match any recording. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/invoice.py b/src/wavix/types/invoice.py new file mode 100644 index 0000000..bc58831 --- /dev/null +++ b/src/wavix/types/invoice.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Invoice(UniversalBaseModel): + """ + An account financial statement + """ + + id: int = pydantic.Field() + """ + Unique identifier of the statement + """ + + amount: str = pydantic.Field() + """ + Statement amount + """ + + from_date: dt.date = pydantic.Field() + """ + Start of the billing period + """ + + to_date: dt.date = pydantic.Field() + """ + End of the billing period + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/invoice_list_response.py b/src/wavix/types/invoice_list_response.py new file mode 100644 index 0000000..79ebdc8 --- /dev/null +++ b/src/wavix/types/invoice_list_response.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .invoice import Invoice +from .pagination import Pagination + + +class InvoiceListResponse(UniversalBaseModel): + is_empty: bool = pydantic.Field() + """ + Indicates whether the statement list is empty. + """ + + invoices: typing.List[Invoice] = pydantic.Field() + """ + Auto-generated statements for the account. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/list_brand_evidence_response.py b/src/wavix/types/list_brand_evidence_response.py new file mode 100644 index 0000000..0783b33 --- /dev/null +++ b/src/wavix/types/list_brand_evidence_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_evidence import TenDlcBrandEvidence + + +class ListBrandEvidenceResponse(UniversalBaseModel): + items: typing.List[TenDlcBrandEvidence] = pydantic.Field() + """ + List of uploaded evidence files + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/list_sessions_response_item.py b/src/wavix/types/list_sessions_response_item.py new file mode 100644 index 0000000..e06e617 --- /dev/null +++ b/src/wavix/types/list_sessions_response_item.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ListSessionsResponseItem(UniversalBaseModel): + created_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time the 2FA Verification was created in ISO 8601 format. + """ + + session_id: typing.Optional[str] = pydantic.Field(default=None) + """ + 2FA Verification ID. + """ + + phone_number: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination phone number. + """ + + destination_country: typing.Optional[str] = pydantic.Field(default=None) + """ + Country code of the destination phone number in ISO 3166-1 alpha-2 format. + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Status of the 2FA Verification. + """ + + charge: typing.Optional[str] = pydantic.Field(default=None) + """ + Charge for the 2FA Verification. + """ + + service_id: typing.Optional[str] = pydantic.Field(default=None) + """ + 2FA Service ID. + """ + + service_name: typing.Optional[str] = pydantic.Field(default=None) + """ + 2FA Service name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/message.py b/src/wavix/types/message.py new file mode 100644 index 0000000..039a97b --- /dev/null +++ b/src/wavix/types/message.py @@ -0,0 +1,103 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_body import MessageBody +from .message_delivery_status import MessageDeliveryStatus + + +class Message(UniversalBaseModel): + """ + SMS or MMS message. + """ + + message_id: str = pydantic.Field() + """ + Message ID. + """ + + message_type: str = pydantic.Field() + """ + Message type. Possible values are `sms` or `mms` + """ + + from_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Sender ID. Can be numeric or alphanumeric."), + ] + to: str = pydantic.Field() + """ + Recipient phone number. + """ + + carrier_fees: typing.Optional[str] = pydantic.Field(default=None) + """ + Carrier fees for the message in USD. + """ + + direction: str = pydantic.Field() + """ + Message direction. Possible values are `outbound` or `inbound`. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile country code. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile network code. + """ + + message_body: MessageBody + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Tag to group messages, such as for a specific campaign. + """ + + status: MessageDeliveryStatus + segments: int = pydantic.Field() + """ + Number of SMS segments. Always 1 for MMS. + """ + + charge: str = pydantic.Field() + """ + Total charge for the message in USD. + """ + + submitted_at: str = pydantic.Field() + """ + Date and time the message was accepted in ISO 8601 format. + """ + + sent_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was sent in ISO 8601 format. For mobile terminated messages only. + """ + + delivered_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was delivered in ISO 8601 format. Refers to DLR reception for mobile-terminated messages + or webhook relay for mobile-originated messages. + """ + + error_message: typing.Optional[str] = pydantic.Field(default=None) + """ + A human-readable error description. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/message_body.py b/src/wavix/types/message_body.py new file mode 100644 index 0000000..4ee0038 --- /dev/null +++ b/src/wavix/types/message_body.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class MessageBody(UniversalBaseModel): + text: str = pydantic.Field() + """ + Message text. + """ + + media: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of media URLs. + If provided, the message is sent as an MMS; + otherwise, it is sent as an SMS. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/message_create_request.py b/src/wavix/types/message_create_request.py new file mode 100644 index 0000000..216d741 --- /dev/null +++ b/src/wavix/types/message_create_request.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_body import MessageBody + + +class MessageCreateRequest(UniversalBaseModel): + from_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Sender ID. Numeric or alphanumeric."), + ] + to: str = pydantic.Field() + """ + Recipient phone number. + """ + + message_body: MessageBody + callback_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Callback URL for delivery reports. + """ + + validity: typing.Optional[int] = pydantic.Field(default=None) + """ + Message validity period in seconds. Delivery attempts stop after this period expires. + """ + + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Tag to group messages, such as for a specific campaign. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/message_delivery_status.py b/src/wavix/types/message_delivery_status.py new file mode 100644 index 0000000..1d0d530 --- /dev/null +++ b/src/wavix/types/message_delivery_status.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +MessageDeliveryStatus = typing.Union[ + typing.Literal["accepted", "pending", "sent", "delivered", "undelivered", "expired", "rejected", "dlr_expired"], + typing.Any, +] diff --git a/src/wavix/types/message_list_response.py b/src/wavix/types/message_list_response.py new file mode 100644 index 0000000..34614b2 --- /dev/null +++ b/src/wavix/types/message_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .message import Message +from .pagination import Pagination + + +class MessageListResponse(UniversalBaseModel): + items: typing.List[Message] = pydantic.Field() + """ + Messages that match the request. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/message_response.py b/src/wavix/types/message_response.py new file mode 100644 index 0000000..7e190d0 --- /dev/null +++ b/src/wavix/types/message_response.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_body import MessageBody +from .message_delivery_status import MessageDeliveryStatus + + +class MessageResponse(UniversalBaseModel): + message_id: str = pydantic.Field() + """ + Message ID. + """ + + message_type: str = pydantic.Field() + """ + Message type. Possible values are `sms`, `mms`. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Sender ID.") + ] + to: str = pydantic.Field() + """ + Recipient phone number. + """ + + direction: str = pydantic.Field() + """ + Message direction. Possible values are `outbound`, `inbound`. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile country code. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile network code. + """ + + message_body: MessageBody + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Tag to group messages, such as for a specific campaign. + """ + + status: MessageDeliveryStatus + segments: int = pydantic.Field() + """ + Number of SMS segments. Always 1 for MMS. + """ + + charge: str = pydantic.Field() + """ + Total charge for the message in USD. + """ + + submitted_at: str = pydantic.Field() + """ + Date and time the message was accepted in ISO 8601 format. + """ + + sent_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was sent in ISO 8601 format. For mobile-terminated messages only. + """ + + delivered_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was delivered in ISO 8601 format. Refers to DLR reception for mobile-terminated messages + or webhook relay for mobile-originated messages. + """ + + error_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error message. + """ + + carrier_fees: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile carrier fees in USD. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/messages_delivery_report.py b/src/wavix/types/messages_delivery_report.py new file mode 100644 index 0000000..388e07e --- /dev/null +++ b/src/wavix/types/messages_delivery_report.py @@ -0,0 +1,81 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_delivery_status import MessageDeliveryStatus + + +class MessagesDeliveryReport(UniversalBaseModel): + """ + Message delivery report (DLR). + """ + + message_id: str = pydantic.Field() + """ + Message ID. + """ + + message_type: str = pydantic.Field() + """ + Type of message. Possible values are `sms`, `mms`. + """ + + from_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="from"), + pydantic.Field(alias="from", description="Sender ID. Can be numeric or alphanumeric."), + ] + to: str = pydantic.Field() + """ + Destination phone number. + """ + + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Tag to identify a group of messages, such as those associated with a campaign. + """ + + status: MessageDeliveryStatus + segments_count: int = pydantic.Field() + """ + Number of message segments for SMS. For MMS, the value is always 1. + """ + + sent: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time the message was accepted in ISO 8601 format. + """ + + delivered: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time when the final status was received in ISO 8601 format. + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable error description. + """ + + charge: str = pydantic.Field() + """ + Total charge for the message in USD. + """ + + carrier_fees: str = pydantic.Field() + """ + Mobile carrier fees in USD. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/not_found_error_body.py b/src/wavix/types/not_found_error_body.py new file mode 100644 index 0000000..2bc0506 --- /dev/null +++ b/src/wavix/types/not_found_error_body.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class NotFoundErrorBody(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/not_found_error_response.py b/src/wavix/types/not_found_error_response.py new file mode 100644 index 0000000..346d68f --- /dev/null +++ b/src/wavix/types/not_found_error_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class NotFoundErrorResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable description stating that no resource matches the given ID. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number.py b/src/wavix/types/number.py new file mode 100644 index 0000000..bdffd45 --- /dev/null +++ b/src/wavix/types/number.py @@ -0,0 +1,160 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .inbound_call_destination import InboundCallDestination +from .number_document import NumberDocument + + +class Number(UniversalBaseModel): + id: int = pydantic.Field() + """ + Phone number ID. + """ + + number: str = pydantic.Field() + """ + Phone number. + """ + + activation_fee: str = pydantic.Field() + """ + One-time activation fee in USD. + """ + + monthly_fee: str = pydantic.Field() + """ + Monthly fee in USD. + """ + + per_min: str = pydantic.Field() + """ + Price per inbound minute in USD. + """ + + city: str = pydantic.Field() + """ + City or rate center where the phone number originates. + """ + + state: typing.Optional[str] = pydantic.Field(default=None) + """ + State where the phone number originates. For non-US numbers, this field may be null. + """ + + country: str = pydantic.Field() + """ + Country where the phone number originates. + """ + + country_short_name: str = pydantic.Field() + """ + Two-letter ISO country code. + """ + + destination: typing.List[InboundCallDestination] = pydantic.Field() + """ + Inbound call destinations set for the phone number. + """ + + channels: int = pydantic.Field() + """ + Maximum number of concurrent inbound calls. + """ + + require_docs: typing.List[str] = pydantic.Field() + """ + Documents required to activate the phone number. + """ + + documents: typing.List[NumberDocument] = pydantic.Field() + """ + Uploaded documents for the phone number. + """ + + domestic_cli: bool = pydantic.Field() + """ + Indicates whether the number can be used as the Caller ID for local calls. + """ + + free_min: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of free inbound minutes. + """ + + unlimited: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether usage is unlimited. + """ + + label: typing.Optional[str] = pydantic.Field(default=None) + """ + Label assigned to the phone number. + """ + + status: str = pydantic.Field() + """ + Phone number status. `active` means the number can receive and place calls; `inactive` means it cannot. + """ + + seconds: str = pydantic.Field() + """ + Total inbound call duration in seconds for current month. + """ + + added: dt.datetime = pydantic.Field() + """ + Date and time the phone number was purchased in ISO 8601 format. + """ + + paid_until: dt.date = pydantic.Field() + """ + Date until which the number is paid. + """ + + sms_enabled: bool = pydantic.Field() + """ + Indicates whether SMS is enabled. + """ + + sms_relay_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Callback URL for inbound SMS and MMS messages. + """ + + cnam: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether CNAM is enabled. + """ + + call_recording_enabled: bool = pydantic.Field() + """ + Indicates whether call recording is enabled. + """ + + transcription_enabled: bool = pydantic.Field() + """ + Indicates whether transcription is enabled. + """ + + transcription_threshold: int = pydantic.Field() + """ + Minimum call duration in seconds to trigger transcription. + """ + + call_status_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Callback URL for call status updates. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number_destination.py b/src/wavix/types/number_destination.py new file mode 100644 index 0000000..1be8c65 --- /dev/null +++ b/src/wavix/types/number_destination.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .inbound_call_transport import InboundCallTransport + + +class NumberDestination(UniversalBaseModel): + """ + Inbound call destination + """ + + destination: str = pydantic.Field() + """ + SIP URI, PSTN number, or SIP trunk login for inbound call routing. + """ + + priority: int = pydantic.Field() + """ + Destination priority (lower is higher priority). + """ + + transport: InboundCallTransport + trunk_id: typing.Optional[int] = pydantic.Field(default=None) + """ + SIP trunk ID when `transport` is 5, otherwise null. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number_document.py b/src/wavix/types/number_document.py new file mode 100644 index 0000000..1b92572 --- /dev/null +++ b/src/wavix/types/number_document.py @@ -0,0 +1,58 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .document_type_id import DocumentTypeId + + +class NumberDocument(UniversalBaseModel): + """ + A document uploaded for a phone number + """ + + id: int = pydantic.Field() + """ + Unique identifier of the uploaded document + """ + + allow_replace: bool = pydantic.Field() + """ + Indicates whether the document can be replaced. Only documents with `rejected` status can be replaced. + """ + + did_number: str = pydantic.Field() + """ + The phone number the document was uploaded for + """ + + doc_content_type: str = pydantic.Field() + """ + The uploaded content type identified by the platform + """ + + doc_file_name: str = pydantic.Field() + """ + The uploaded document name + """ + + doc_type_id: DocumentTypeId + status: str = pydantic.Field() + """ + Status of the uploaded document. Can be either `approved`, `pending`, or `rejected` + """ + + url: str = pydantic.Field() + """ + A link to the uploaded document + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number_list_response.py b/src/wavix/types/number_list_response.py new file mode 100644 index 0000000..e996fca --- /dev/null +++ b/src/wavix/types/number_list_response.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .document_type import DocumentType +from .number import Number +from .pagination import Pagination + + +class NumberListResponse(UniversalBaseModel): + items: typing.List[Number] = pydantic.Field() + """ + List of phone numbers on the account. + """ + + doc_types: typing.List[DocumentType] = pydantic.Field() + """ + Documents required to activate phone numbers. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number_status_updated_webhook.py b/src/wavix/types/number_status_updated_webhook.py new file mode 100644 index 0000000..8b85216 --- /dev/null +++ b/src/wavix/types/number_status_updated_webhook.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .number_status_updated_webhook_status import NumberStatusUpdatedWebhookStatus + + +class NumberStatusUpdatedWebhook(UniversalBaseModel): + brand_id: str = pydantic.Field() + """ + 10DLC Brand ID. + """ + + campaign_id: str = pydantic.Field() + """ + 10DLC Campaign ID. + """ + + number: str = pydantic.Field() + """ + Phone number associated with a 10DLC Campaign. + """ + + status: NumberStatusUpdatedWebhookStatus = pydantic.Field() + """ + Status of the phone number's assignment to the 10DLC Campaign. One of `APPROVED` (the number is registered to the campaign), `PENDING` (registration is in progress), or `REJECTED` (registration was declined). + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/number_status_updated_webhook_status.py b/src/wavix/types/number_status_updated_webhook_status.py new file mode 100644 index 0000000..a1c4c41 --- /dev/null +++ b/src/wavix/types/number_status_updated_webhook_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +NumberStatusUpdatedWebhookStatus = typing.Union[typing.Literal["APPROVED", "PENDING", "REJECTED"], typing.Any] diff --git a/src/wavix/types/number_validator_create_bulk_response.py b/src/wavix/types/number_validator_create_bulk_response.py new file mode 100644 index 0000000..9349da7 --- /dev/null +++ b/src/wavix/types/number_validator_create_bulk_response.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .number_validator_create_bulk_response_request_id import NumberValidatorCreateBulkResponseRequestId +from .phone_validation_batch_response import PhoneValidationBatchResponse + +NumberValidatorCreateBulkResponse = typing.Union[ + PhoneValidationBatchResponse, NumberValidatorCreateBulkResponseRequestId +] diff --git a/src/wavix/types/number_validator_create_bulk_response_request_id.py b/src/wavix/types/number_validator_create_bulk_response_request_id.py new file mode 100644 index 0000000..d97f996 --- /dev/null +++ b/src/wavix/types/number_validator_create_bulk_response_request_id.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class NumberValidatorCreateBulkResponseRequestId(UniversalBaseModel): + """ + Returned when validation runs asynchronously; poll results using `request_id`. + """ + + request_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Request ID. Use to poll validation results when `async` is `true`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/on_call_event_payload.py b/src/wavix/types/on_call_event_payload.py new file mode 100644 index 0000000..3a262bd --- /dev/null +++ b/src/wavix/types/on_call_event_payload.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .on_call_event_payload_type import OnCallEventPayloadType + + +class OnCallEventPayload(UniversalBaseModel): + """ + Payload for the `on_call_event` event type. + """ + + type: OnCallEventPayloadType = pydantic.Field() + """ + Type of in-call sub-event. One of `audio` (audio playback progress) or `collect` (DTMF digit collection progress). + """ + + payload: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Sub-event-specific data. Structure depends on the `type` field: + - `audio`: `{ "status": "started" | "completed", "playback_id": "" }` + - `collect`: `{ "digits": "", "status": "started" | "completed" | "failed" }` + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/on_call_event_payload_type.py b/src/wavix/types/on_call_event_payload_type.py new file mode 100644 index 0000000..a3ce9c0 --- /dev/null +++ b/src/wavix/types/on_call_event_payload_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +OnCallEventPayloadType = typing.Union[typing.Literal["audio", "collect"], typing.Any] diff --git a/src/wavix/types/opt_out.py b/src/wavix/types/opt_out.py new file mode 100644 index 0000000..4ddd9bf --- /dev/null +++ b/src/wavix/types/opt_out.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class OptOut(UniversalBaseModel): + number: str = pydantic.Field() + """ + Phone number to opt out. + """ + + sender_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Sender ID. If omitted, the phone number is opted out of all messages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/opt_out_item.py b/src/wavix/types/opt_out_item.py new file mode 100644 index 0000000..f529d5a --- /dev/null +++ b/src/wavix/types/opt_out_item.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class OptOutItem(UniversalBaseModel): + phone_number: typing.Optional[str] = pydantic.Field(default=None) + """ + Opted-out phone number. + """ + + sender_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Sender ID. Contains `null` if the phone number opted out of all messages. + """ + + campaign_id: typing.Optional[str] = pydantic.Field(default=None) + """ + 10DLC campaign ID, if any. + """ + + created_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Opt-out date and time in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/opt_outs_list_response.py b/src/wavix/types/opt_outs_list_response.py new file mode 100644 index 0000000..ee6c966 --- /dev/null +++ b/src/wavix/types/opt_outs_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .opt_out_item import OptOutItem +from .pagination import Pagination + + +class OptOutsListResponse(UniversalBaseModel): + items: typing.Optional[typing.List[OptOutItem]] = pydantic.Field(default=None) + """ + Opt-out records that match the request. + """ + + pagination: typing.Optional[Pagination] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/pagination.py b/src/wavix/types/pagination.py new file mode 100644 index 0000000..d9340f3 --- /dev/null +++ b/src/wavix/types/pagination.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Pagination(UniversalBaseModel): + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/phone_lookup_details.py b/src/wavix/types/phone_lookup_details.py new file mode 100644 index 0000000..cc9d8e7 --- /dev/null +++ b/src/wavix/types/phone_lookup_details.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class PhoneLookupDetails(UniversalBaseModel): + number_type: str = pydantic.Field() + """ + The destination phone number type + """ + + country: str = pydantic.Field() + """ + The destination phone number's 2-letter ISO country code + """ + + current_carrier: str = pydantic.Field() + """ + The carrier name the phone number currently belongs to + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/phone_number_validation_type.py b/src/wavix/types/phone_number_validation_type.py new file mode 100644 index 0000000..a624c51 --- /dev/null +++ b/src/wavix/types/phone_number_validation_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +PhoneNumberValidationType = typing.Union[typing.Literal["format", "analysis", "validation"], typing.Any] diff --git a/src/wavix/types/phone_validation_batch_response.py b/src/wavix/types/phone_validation_batch_response.py new file mode 100644 index 0000000..69809cb --- /dev/null +++ b/src/wavix/types/phone_validation_batch_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .phone_validation_result_response import PhoneValidationResultResponse + + +class PhoneValidationBatchResponse(UniversalBaseModel): + status: str = pydantic.Field() + """ + Validation request status. + """ + + pending: int = pydantic.Field() + """ + Number of validations still in progress. + """ + + count: int = pydantic.Field() + """ + Total number of phone numbers in the request. + """ + + items: typing.List[PhoneValidationResultResponse] = pydantic.Field() + """ + List of validation results for each phone number. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/phone_validation_batch_result_response.py b/src/wavix/types/phone_validation_batch_result_response.py new file mode 100644 index 0000000..fb28465 --- /dev/null +++ b/src/wavix/types/phone_validation_batch_result_response.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .phone_validation_result_response import PhoneValidationResultResponse + + +class PhoneValidationBatchResultResponse(UniversalBaseModel): + status: str = pydantic.Field() + """ + Request status. Possible values are `success` and `in progress`. + + - `success`: All numbers were processed. - `in progress`: Several numbers are pending validation. + """ + + pending: int = pydantic.Field() + """ + The quantity of phone numbers that are pending to be processed. + """ + + count: int = pydantic.Field() + """ + The quantity of phone numbers passed in the request. + """ + + items: typing.List[PhoneValidationResultResponse] = pydantic.Field() + """ + Array of objects containing details for each phone number. + The returned fields depend on the `type` parameter. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/phone_validation_response.py b/src/wavix/types/phone_validation_response.py new file mode 100644 index 0000000..6ec8345 --- /dev/null +++ b/src/wavix/types/phone_validation_response.py @@ -0,0 +1,112 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PhoneValidationResponse(UniversalBaseModel): + phone_number: str = pydantic.Field() + """ + Phone number. + """ + + valid: bool = pydantic.Field() + """ + Indicates whether the phone number is valid. + """ + + country_code: typing.Optional[str] = pydantic.Field(default=None) + """ + ISO 3166-1 alpha-2 country code of the phone number. + `null` if the number is invalid. + """ + + e164format: typing_extensions.Annotated[ + str, + FieldMetadata(alias="e164_format"), + pydantic.Field(alias="e164_format", description="Phone number in international E.164 format."), + ] + national_format: str = pydantic.Field() + """ + Phone number in the national format of the identified country. + """ + + ported: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number was ported or not. `null` if the phone number is invalid. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Country Code of the phone number carrier. For mobile phone numbers only. `null` if the phone number is invalid. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Network Code of the phone number carrier. For mobile phone numbers only. `null` if the phone number is invalid + """ + + number_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Number type. Possible values are `mobile`, `landline`, or `toll-free`. `null` if the phone number is invalid. + """ + + carrier_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Name of the phone number carrier. `null` if the phone number is invalid. + """ + + risky_destination: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to a number range associated with traffic pumping. `null` if the number is invalid. + """ + + unallocated_range: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to an unallocated number range. `null` if the number is invalid + """ + + reachable: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is registered in a mobile network. For mobile phone numbers only. `null` if the number is invalid + """ + + roaming: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is roaming. For mobile phone numbers only. `null` if the number is invalid + """ + + timezone: typing.Optional[str] = pydantic.Field(default=None) + """ + Time zone based on the phone number's country and area code. + `null` if the number is invalid. + """ + + charge: str = pydantic.Field() + """ + Charge for the validation. + """ + + error_code: str = pydantic.Field() + """ + Error code for the request. `000` indicates success. + Possible values: + - `013`: — Internal service error + - `021`: — Invalid phone number length or format + - `041`: — Request timeout + - `042`: — Request failed + - `091`: — Insufficient funds + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/phone_validation_result_response.py b/src/wavix/types/phone_validation_result_response.py new file mode 100644 index 0000000..bf7da62 --- /dev/null +++ b/src/wavix/types/phone_validation_result_response.py @@ -0,0 +1,115 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class PhoneValidationResultResponse(UniversalBaseModel): + """ + Number validator response details. + """ + + phone_number: str = pydantic.Field() + """ + Phone number. + """ + + valid: bool = pydantic.Field() + """ + Indicates whether the phone number is valid. + """ + + country_code: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number country code in ISO 3166-1 alpha-2 format. `null` if the number is invalid. + """ + + e164format: typing_extensions.Annotated[ + str, + FieldMetadata(alias="e164_format"), + pydantic.Field(alias="e164_format", description="Phone number in international E.164 format."), + ] + national_format: str = pydantic.Field() + """ + Phone number in the national format of the identified country. + """ + + ported: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number is ported. `null` if the phone number is invalid. + """ + + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Country Code (MCC). Applies to mobile numbers only. `null` if the phone number is invalid. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile Network Code (MNC). Applies to mobile numbers only. `null` if the phone number is invalid. + """ + + number_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number type. Allowed values: `mobile`, `landline`, `toll-free`. `null` if the phone number is invalid. + """ + + carrier_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Carrier name. `null` if the phone number is invalid. + """ + + risky_destination: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to a range associated with traffic pumping. `null` if the phone number is invalid. + """ + + unallocated_range: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the phone number belongs to an unallocated range. + `null` if the phone number is invalid. + """ + + reachable: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is registered in a mobile network. Applies to mobile numbers only. `null` if the phone number is invalid. + """ + + roaming: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the number is roaming. Applies to mobile numbers only. + `null` if the phone number is invalid. + """ + + timezone: typing.Optional[str] = pydantic.Field(default=None) + """ + Time zone identified based on the country and area code. `null` if the phone number is invalid. + """ + + charge: str = pydantic.Field() + """ + Charge for the validation. + """ + + error_code: str = pydantic.Field() + """ + Result code for the validation. `000` indicates success. Possible values: + - `013` — internal service error, uncategorized. + - `021` — invalid phone number length or format. + - `041` — remote timeout. + - `042` — remote query failed. + - `091` — insufficient funds. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/profile_config_response.py b/src/wavix/types/profile_config_response.py new file mode 100644 index 0000000..be53987 --- /dev/null +++ b/src/wavix/types/profile_config_response.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .account_limits import AccountLimits + + +class ProfileConfigResponse(UniversalBaseModel): + balance: str = pydantic.Field() + """ + Funds available on the account balance, in USD + """ + + global_limits: AccountLimits + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/profile_response.py b/src/wavix/types/profile_response.py new file mode 100644 index 0000000..be83db1 --- /dev/null +++ b/src/wavix/types/profile_response.py @@ -0,0 +1,79 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .profile_response_company_info import ProfileResponseCompanyInfo +from .profile_response_default_destinations_item import ProfileResponseDefaultDestinationsItem + + +class ProfileResponse(UniversalBaseModel): + id: int = pydantic.Field() + """ + Account ID. + """ + + email: str = pydantic.Field() + """ + Email associated with the account. + """ + + first_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Account owner's first name. + """ + + last_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Account owner's last name. + """ + + phone: typing.Optional[str] = pydantic.Field(default=None) + """ + Account owner's phone number. + """ + + additional_info: typing.Optional[str] = pydantic.Field(default=None) + """ + Additional info associated with the account. + """ + + contact_email: typing.Optional[str] = pydantic.Field(default=None) + """ + Additional email address for billing notifications. + """ + + timezone: str = pydantic.Field() + """ + Timezone configured on the account. + """ + + job_title: typing.Optional[str] = pydantic.Field(default=None) + """ + Account owner's job title. + """ + + default_short_link_endpoint: typing.Optional[str] = pydantic.Field(default=None) + """ + Default short link endpoint. + """ + + default_destinations: typing.List[ProfileResponseDefaultDestinationsItem] = pydantic.Field() + """ + Default destinations configured for the account. + """ + + company_info: typing.Optional[ProfileResponseCompanyInfo] = pydantic.Field(default=None) + """ + Company details. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/profile_response_company_info.py b/src/wavix/types/profile_response_company_info.py new file mode 100644 index 0000000..f40be7c --- /dev/null +++ b/src/wavix/types/profile_response_company_info.py @@ -0,0 +1,53 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .profile_response_company_info_country import ProfileResponseCompanyInfoCountry +from .profile_response_company_info_industry import ProfileResponseCompanyInfoIndustry + + +class ProfileResponseCompanyInfo(UniversalBaseModel): + """ + Company details. + """ + + name: typing.Optional[str] = pydantic.Field(default=None) + """ + Company name. + """ + + industry: typing.Optional[ProfileResponseCompanyInfoIndustry] = pydantic.Field(default=None) + """ + Industry the company operates in. One of `telecommunications`, `information technology and services`, `fintech and finance`, `healthcare and pharmaceuticals`, `ecommerce and retail`, `education and research`, `pickup and delivery`, `transportation and logistics`, `media and entertainment`, `travel and hospitality`, `non-profit and charity organizations`, `manufacturing and industrial goods`, or `other`. Each value names the company's sector. `null` when the industry is not set. + """ + + address: typing.Optional[str] = pydantic.Field(default=None) + """ + Company address. + """ + + attn_contact_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Billing contact name. + """ + + vat_number: typing.Optional[str] = pydantic.Field(default=None) + """ + VAT number. + """ + + country: typing.Optional[ProfileResponseCompanyInfoCountry] = pydantic.Field(default=None) + """ + Country. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/profile_response_company_info_country.py b/src/wavix/types/profile_response_company_info_country.py new file mode 100644 index 0000000..2f2e18c --- /dev/null +++ b/src/wavix/types/profile_response_company_info_country.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ProfileResponseCompanyInfoCountry(UniversalBaseModel): + """ + Country. + """ + + country_name: typing.Optional[str] = pydantic.Field(default=None) + """ + Country name. + """ + + country_id: typing.Optional[int] = pydantic.Field(default=None) + """ + Country ID. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/profile_response_company_info_industry.py b/src/wavix/types/profile_response_company_info_industry.py new file mode 100644 index 0000000..5f8f6c8 --- /dev/null +++ b/src/wavix/types/profile_response_company_info_industry.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +ProfileResponseCompanyInfoIndustry = typing.Union[ + typing.Literal[ + "telecommunications", + "information technology and services", + "fintech and finance", + "healthcare and pharmaceuticals", + "ecommerce and retail", + "education and research", + "pickup and delivery", + "transportation and logistics", + "media and entertainment", + "travel and hospitality", + "non-profit and charity organizations", + "manufacturing and industrial goods", + "other", + ], + typing.Any, +] diff --git a/src/wavix/types/profile_response_default_destinations_item.py b/src/wavix/types/profile_response_default_destinations_item.py new file mode 100644 index 0000000..7ea9eee --- /dev/null +++ b/src/wavix/types/profile_response_default_destinations_item.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ProfileResponseDefaultDestinationsItem(UniversalBaseModel): + transport: typing.Optional[str] = pydantic.Field(default=None) + """ + Transport type. + """ + + value: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination value + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/record_not_found_error_response.py b/src/wavix/types/record_not_found_error_response.py new file mode 100644 index 0000000..5f96ae8 --- /dev/null +++ b/src/wavix/types/record_not_found_error_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RecordNotFoundErrorResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/recording.py b/src/wavix/types/recording.py new file mode 100644 index 0000000..21afb31 --- /dev/null +++ b/src/wavix/types/recording.py @@ -0,0 +1,57 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class Recording(UniversalBaseModel): + """ + A call recording response + """ + + id: int = pydantic.Field() + """ + Recording ID. + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time when the recording was created in ISO 8601 format. + """ + + duration: int = pydantic.Field() + """ + Recording duration in seconds. + """ + + from_: typing_extensions.Annotated[ + str, FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Originating phone number.") + ] + to: str = pydantic.Field() + """ + Destination phone number. + """ + + call_uuid: str = pydantic.Field() + """ + Call ID. + """ + + url: str = pydantic.Field() + """ + Recording file URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/recording_deleted_error_response.py b/src/wavix/types/recording_deleted_error_response.py new file mode 100644 index 0000000..1cf6a68 --- /dev/null +++ b/src/wavix/types/recording_deleted_error_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class RecordingDeletedErrorResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + error: typing.Optional[bool] = pydantic.Field(default=None) + """ + Error flag. Always `true` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable explanation of why the recording is unavailable. + """ + + deleted_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Timestamp when the recording was deleted, in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/region.py b/src/wavix/types/region.py new file mode 100644 index 0000000..8863a95 --- /dev/null +++ b/src/wavix/types/region.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Region(UniversalBaseModel): + """ + State or province of the country, if applicable. + """ + + id: int = pydantic.Field() + """ + Region ID. + """ + + name: str = pydantic.Field() + """ + Region name. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/region_list_response.py b/src/wavix/types/region_list_response.py new file mode 100644 index 0000000..d55d18d --- /dev/null +++ b/src/wavix/types/region_list_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .region import Region + + +class RegionListResponse(UniversalBaseModel): + regions: typing.List[Region] = pydantic.Field() + """ + States or provinces available for the requested country. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/send_messages_response.py b/src/wavix/types/send_messages_response.py new file mode 100644 index 0000000..30e8e74 --- /dev/null +++ b/src/wavix/types/send_messages_response.py @@ -0,0 +1,100 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata +from .message_body import MessageBody + + +class SendMessagesResponse(UniversalBaseModel): + carrier_fees: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile carrier fees in USD. + """ + + charge: typing.Optional[str] = pydantic.Field(default=None) + """ + Total charge for the message in USD. + """ + + direction: typing.Optional[str] = pydantic.Field(default=None) + """ + Message direction. Possible values are `outbound`, `inbound`. + """ + + delivered_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time the message was delivered in ISO 8601 format. + """ + + error_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Error message. + """ + + from_: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="from"), pydantic.Field(alias="from", description="Sender ID.") + ] = None + mcc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile country code. + """ + + mnc: typing.Optional[str] = pydantic.Field(default=None) + """ + Mobile network code. + """ + + message_body: typing.Optional[MessageBody] = None + message_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Message ID. + """ + + message_type: typing.Optional[str] = pydantic.Field(default=None) + """ + Message type. + """ + + segments: typing.Optional[int] = pydantic.Field(default=None) + """ + Number of SMS segments. Always 1 for MMS. + """ + + sent_at: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Date and time the message was sent in ISO 8601 format. + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Message status. + """ + + submitted_at: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the message was submitted in ISO 8601 format. + """ + + tag: typing.Optional[str] = pydantic.Field(default=None) + """ + Message tag. + """ + + to: typing.Optional[str] = pydantic.Field(default=None) + """ + Recipient phone number. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sender_id.py b/src/wavix/types/sender_id.py new file mode 100644 index 0000000..2d598ab --- /dev/null +++ b/src/wavix/types/sender_id.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sender_id_type import SenderIdType + + +class SenderId(UniversalBaseModel): + """ + SMS Sender ID. + """ + + id: str = pydantic.Field() + """ + Sender ID ID. + """ + + sender_id: str = pydantic.Field() + """ + Sender ID name. + """ + + type: SenderIdType + allowlisted_in: typing.List[str] = pydantic.Field() + """ + Two-letter ISO country codes where the Sender ID is allowlisted. + """ + + usecase: typing.Optional[str] = pydantic.Field(default=None) + """ + Primary use case declared for the Sender ID, such as `transactional`, `promo`, or `authentication`. + """ + + samples: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Message samples. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sender_id_details.py b/src/wavix/types/sender_id_details.py new file mode 100644 index 0000000..70ffe39 --- /dev/null +++ b/src/wavix/types/sender_id_details.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sender_id_type import SenderIdType + + +class SenderIdDetails(UniversalBaseModel): + id: str = pydantic.Field() + """ + Sender ID ID. + """ + + sender_id: str = pydantic.Field() + """ + Sender ID name. + """ + + type: SenderIdType + allowlisted_in: typing.List[str] = pydantic.Field() + """ + List of countries where the Sender ID is allowlisted. + """ + + usecase: typing.Optional[str] = pydantic.Field(default=None) + """ + Primary use case declared for the Sender ID, such as `transactional`, `promo`, or `authentication`. + """ + + samples: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Message samples. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sender_id_list_response.py b/src/wavix/types/sender_id_list_response.py new file mode 100644 index 0000000..8ac76b7 --- /dev/null +++ b/src/wavix/types/sender_id_list_response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sender_id import SenderId + + +class SenderIdListResponse(UniversalBaseModel): + items: typing.List[SenderId] = pydantic.Field() + """ + List of Sender IDs. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sender_id_response.py b/src/wavix/types/sender_id_response.py new file mode 100644 index 0000000..116014b --- /dev/null +++ b/src/wavix/types/sender_id_response.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sender_id_response_type import SenderIdResponseType + + +class SenderIdResponse(UniversalBaseModel): + id: str = pydantic.Field() + """ + Sender ID ID. + """ + + sender_id: str = pydantic.Field() + """ + Sender ID. + """ + + type: SenderIdResponseType = pydantic.Field() + """ + Format of the Sender ID. One of `alphanumeric` (a text sender name), `numeric` (a phone number), or `shortcode` (a short code). + """ + + usecase: typing.Optional[str] = pydantic.Field(default=None) + """ + Use case for the Sender ID. + """ + + samples: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Message samples. + """ + + allowlisted_in: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + List of countries where the Sender ID is allowlisted. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sender_id_response_type.py b/src/wavix/types/sender_id_response_type.py new file mode 100644 index 0000000..166ccc6 --- /dev/null +++ b/src/wavix/types/sender_id_response_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SenderIdResponseType = typing.Union[typing.Literal["alphanumeric", "numeric", "shortcode"], typing.Any] diff --git a/src/wavix/types/sender_id_type.py b/src/wavix/types/sender_id_type.py new file mode 100644 index 0000000..72e0f11 --- /dev/null +++ b/src/wavix/types/sender_id_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SenderIdType = typing.Union[typing.Literal["numeric", "alphanumeric"], typing.Any] diff --git a/src/wavix/types/short_link_metrics_item.py b/src/wavix/types/short_link_metrics_item.py new file mode 100644 index 0000000..b8ccd3f --- /dev/null +++ b/src/wavix/types/short_link_metrics_item.py @@ -0,0 +1,69 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ShortLinkMetricsItem(UniversalBaseModel): + latitude: typing.Optional[float] = pydantic.Field(default=None) + """ + Latitude of the location derived from the IP address + used to open the short link. + """ + + longitude: typing.Optional[float] = pydantic.Field(default=None) + """ + Longitude of the location derived from the IP address + used to open the short link. + """ + + operating_system: typing.Optional[str] = pydantic.Field(default=None) + """ + Operating system of the device that opened the short link. + """ + + browser: typing.Optional[str] = pydantic.Field(default=None) + """ + Browser used to open the short link. + """ + + language: typing.Optional[str] = pydantic.Field(default=None) + """ + Browser language preference. + """ + + phone: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number associated with the short link. + """ + + utm_campaign: typing.Optional[str] = pydantic.Field(default=None) + """ + UTM campaign name associated with the short link. + """ + + created_at: str = pydantic.Field() + """ + Date and time when short link was opened. + """ + + link_hash: str = pydantic.Field() + """ + Hash of the short link. + """ + + user_id: int = pydantic.Field() + """ + Account ID. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/short_link_metrics_response.py b/src/wavix/types/short_link_metrics_response.py new file mode 100644 index 0000000..ec95c61 --- /dev/null +++ b/src/wavix/types/short_link_metrics_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .pagination import Pagination +from .short_link_metrics_item import ShortLinkMetricsItem + + +class ShortLinkMetricsResponse(UniversalBaseModel): + metrics: typing.List[ShortLinkMetricsItem] = pydantic.Field() + """ + List of short link metrics matching search criteria. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/short_link_response.py b/src/wavix/types/short_link_response.py new file mode 100644 index 0000000..14c2398 --- /dev/null +++ b/src/wavix/types/short_link_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ShortLinkResponse(UniversalBaseModel): + short_link: str = pydantic.Field() + """ + Generated short URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_create_request.py b/src/wavix/types/sip_trunk_create_request.py new file mode 100644 index 0000000..66a270e --- /dev/null +++ b/src/wavix/types/sip_trunk_create_request.py @@ -0,0 +1,132 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sip_trunk_create_request_allowed_ips_item import SipTrunkCreateRequestAllowedIpsItem +from .sip_trunk_create_request_host_request import SipTrunkCreateRequestHostRequest + + +class SipTrunkCreateRequest(UniversalBaseModel): + label: str = pydantic.Field() + """ + User-defined name of the SIP trunk + """ + + password: str = pydantic.Field() + """ + Password set for the SIP trunk. A strong password helps keep the trunk secure. + """ + + host_request: typing.Optional[SipTrunkCreateRequestHostRequest] = pydantic.Field(default=None) + """ + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + """ + + callerid: str = pydantic.Field() + """ + Caller ID associated with the SIP trunk. Must be an active or verified number on the account. + """ + + multiple_numbers: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + """ + + ip_restrict: bool = pydantic.Field() + """ + Indicates whether SIP trunk registration is allowed from only specific public static IP addresses. When set to `true`, the `allowed_ips` parameter must be provided. + """ + + allowed_ips: typing.Optional[typing.List[SipTrunkCreateRequestAllowedIpsItem]] = pydantic.Field(default=None) + """ + A list of public static IP addresses allowed to register with the SIP trunk + """ + + didinfo_enabled: bool = pydantic.Field() + """ + Indicates whether inbound calls include dialed number information in the `To` header of SIP INVITE requests + """ + + call_restrict: bool = pydantic.Field() + """ + Indicates whether a maximum call duration limit is enforced for the SIP trunk + """ + + call_limit: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum call duration for the SIP trunk, in seconds. Must not exceed the maximum duration set for the account. Ignored when `call_restrict` is `false`. + """ + + cost_limit: bool = pydantic.Field() + """ + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + """ + + max_call_cost: typing.Optional[float] = pydantic.Field(default=None) + """ + Maximum cost for an outbound call, in USD + """ + + channels_restrict: bool = pydantic.Field() + """ + Indicates whether a limit on the number of concurrent outbound calls is enforced for the SIP trunk + """ + + max_channels: typing.Optional[int] = pydantic.Field(default=None) + """ + Maximum number of concurrent outbound calls for the SIP trunk. Must not exceed the outbound channel capacity set for the account. Ignored when `channels_restrict` is `false`. + """ + + rewrite_enabled: bool = pydantic.Field() + """ + Indicates whether a custom dial plan is activated for the SIP trunk + """ + + rewrite_prefix: typing.Optional[str] = pydantic.Field(default=None) + """ + Digits to automatically prepend to each dialed phone number + """ + + rewrite_cond: typing.Optional[str] = pydantic.Field(default=None) + """ + Number of leading digits to automatically remove from each dialed phone number + """ + + call_recording_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether outbound call recording is enabled for the SIP trunk + """ + + transcription_enabled: bool = pydantic.Field() + """ + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + """ + + transcription_threshold: int = pydantic.Field() + """ + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds. + Available for `Flex Pro` customers only. + """ + + machine_detection_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + """ + + encrypted_media: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether SRTP media encryption is enabled for the SIP trunk. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_create_request_allowed_ips_item.py b/src/wavix/types/sip_trunk_create_request_allowed_ips_item.py new file mode 100644 index 0000000..84c12a7 --- /dev/null +++ b/src/wavix/types/sip_trunk_create_request_allowed_ips_item.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SipTrunkCreateRequestAllowedIpsItem(UniversalBaseModel): + ip: str = pydantic.Field() + """ + Public static IP address allowed to register with the SIP trunk. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_create_request_host_request.py b/src/wavix/types/sip_trunk_create_request_host_request.py new file mode 100644 index 0000000..8b4d54b --- /dev/null +++ b/src/wavix/types/sip_trunk_create_request_host_request.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SipTrunkCreateRequestHostRequest(UniversalBaseModel): + """ + For SIP trunks with IP authentication, includes the SIP endpoint public static IP address and the status of the authentication request. Wavix authenticates all SIP traffic originating from this IP address. + """ + + host: str = pydantic.Field() + """ + SIP endpoint public static IP address + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_list_response.py b/src/wavix/types/sip_trunk_list_response.py new file mode 100644 index 0000000..803fbcd --- /dev/null +++ b/src/wavix/types/sip_trunk_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .pagination import Pagination +from .sip_trunk_summary import SipTrunkSummary + + +class SipTrunkListResponse(UniversalBaseModel): + sip_trunks: typing.Optional[typing.List[SipTrunkSummary]] = pydantic.Field(default=None) + """ + SIP trunks associated with the account. + """ + + pagination: Pagination + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_response.py b/src/wavix/types/sip_trunk_response.py new file mode 100644 index 0000000..c6bc64c --- /dev/null +++ b/src/wavix/types/sip_trunk_response.py @@ -0,0 +1,145 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .allowed_i_ps import AllowedIPs + + +class SipTrunkResponse(UniversalBaseModel): + id: int = pydantic.Field() + """ + Unique identifier of the SIP trunk on the platform + """ + + name: str = pydantic.Field() + """ + System-generated login name of the SIP trunk + """ + + callerid: typing.Optional[str] = pydantic.Field(default=None) + """ + Caller ID configured on the SIP trunk. Contains `null` if no Caller ID is set, or an empty string if multiple Caller IDs are allowed. + """ + + label: str = pydantic.Field() + """ + User-defined name of the SIP trunk + """ + + ip_restrict: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether IP restriction must be enabled on the SIP trunk + """ + + allowed_ips: AllowedIPs + channels_restrict: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if the max number of concurrent outbound calls limit is activated for the SIP trunk. + """ + + max_channels: typing.Optional[int] = pydantic.Field(default=None) + """ + A maximum number of concurrent outbound calls placed via the SIP trunk. Cannot be higher than the outbound channel capacity configured on the account. + """ + + cost_limit: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if the max cost limit for an outbound call limit is activated for the SIP trunk. + """ + + max_call_cost: typing.Optional[str] = pydantic.Field(default=None) + """ + A maximum cost of an outbound call, in USD. + """ + + call_restrict: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if maximum call duration limit is activated for the SIP trunk. + """ + + call_limit: typing.Optional[int] = pydantic.Field(default=None) + """ + A maximum call duration for the SIP trunk, in seconds. Cannot be higher than the max call duration set for the account. + """ + + didinfo_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if inbound calls carry dialed number information in the 'To' header of SIP Invites + """ + + rewrite_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if a custom dial plan is activated for the SIP trunk. + """ + + rewrite_prefix: typing.Optional[str] = pydantic.Field(default=None) + """ + Leading digits to be added before the dialed phone numbers. + """ + + rewrite_cond: typing.Optional[str] = pydantic.Field(default=None) + """ + Leading digits to be deleted from the dialed phone numbers. + """ + + call_recording_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if outbound call recording is enabled on the SIP trunk. Available for `Flex Pro` customers only. + """ + + machine_detection_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if automatic voicemail detection is enabled on the SIP trunk. Available for `Flex Pro` customers only. + """ + + transcription_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates if automatic call transcription is enabled on the SIP trunk. Available for `Flex Pro` customers only. + """ + + transcription_threshold: typing.Optional[int] = pydantic.Field(default=None) + """ + Transcriptions will be generated for calls that meet or exceed the specified minimal call duration threshold, in seconds + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the SIP trunk was created + """ + + host: typing.Optional[str] = pydantic.Field(default=None) + """ + Registration host for the SIP trunk. `dynamic` for credential-based registration, or a public static IP address for IP authentication. + """ + + multiple_numbers: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether any active or verified phone number on the account can be used as the Caller ID for the SIP trunk. + """ + + encrypted_media: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether SRTP media encryption is enabled for the SIP trunk. + """ + + passthrough: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether Caller ID passthrough is enabled. + """ + + access_token: typing.Optional[str] = pydantic.Field(default=None) + """ + Static authentication token for the SIP trunk. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_summary.py b/src/wavix/types/sip_trunk_summary.py new file mode 100644 index 0000000..2276a97 --- /dev/null +++ b/src/wavix/types/sip_trunk_summary.py @@ -0,0 +1,114 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sip_trunk_summary_host_request import SipTrunkSummaryHostRequest + + +class SipTrunkSummary(UniversalBaseModel): + """ + A SIP trunk associated with the account. + """ + + id: int = pydantic.Field() + """ + Unique identifier of the SIP trunk on the platform + """ + + name: str = pydantic.Field() + """ + System-generated login name of the SIP trunk + """ + + callerid: typing.Optional[str] = pydantic.Field(default=None) + """ + Caller ID associated with the SIP trunk. Contains `null` if no Caller ID is set, or an empty string if multiple Caller IDs are allowed. + """ + + label: str = pydantic.Field() + """ + User-defined name of the SIP trunk + """ + + auth_method: str = pydantic.Field() + """ + SIP trunk authentication method. Can be either `Digest` or `IP auth`. + """ + + host_request: typing.Optional[SipTrunkSummaryHostRequest] = pydantic.Field(default=None) + """ + For SIP trunks with IP-based authentication, contains the status of the IP authentication request. + """ + + passthrough: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether Caller ID passthrough is enabled for the SIP trunk + """ + + multiple_numbers: bool = pydantic.Field() + """ + Indicates whether multiple Caller IDs are enabled for the SIP trunk + """ + + status: str = pydantic.Field() + """ + Status of the SIP trunk. Possible values are: + `active` - The SIP trunk is active and can be used to place outbound calls. + `pending` - The IP authentication request is under review by the Wavix team. + `rejected` - The IP authentication request was rejected by the Wavix team. + SIP trunks with a `pending` or `rejected` status can't be used to place calls. + """ + + talk_time: int = pydantic.Field() + """ + Total duration of all outbound calls placed through the SIP trunk the current month, in seconds. Automatically resets at the beginning of each month. + """ + + charge: str = pydantic.Field() + """ + Total cost of all outbound calls placed through the SIP trunk in the current month, in USD. Automatically resets at the beginning of each month. + """ + + call_recording_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether outbound call recording is enabled for the SIP trunk + """ + + machine_detection_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether automatic voicemail detection is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + """ + + transcription_enabled: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether automatic call transcription is enabled for the SIP trunk. + Available for `Flex Pro` customers only. + """ + + transcription_threshold: typing.Optional[int] = pydantic.Field(default=None) + """ + Minimum call duration (in seconds) required to automatically generate a transcription. Transcriptions are created for calls that meet or exceed this value. + Available to `Flex Pro` customers only. + """ + + encrypted_media: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether SRTP media encryption is enabled for the SIP trunk. + """ + + access_token: typing.Optional[str] = pydantic.Field(default=None) + """ + Static authentication token for the SIP trunk. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sip_trunk_summary_host_request.py b/src/wavix/types/sip_trunk_summary_host_request.py new file mode 100644 index 0000000..d9dc747 --- /dev/null +++ b/src/wavix/types/sip_trunk_summary_host_request.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SipTrunkSummaryHostRequest(UniversalBaseModel): + """ + For SIP trunks with IP-based authentication, contains the status of the IP authentication request. + """ + + host: typing.Optional[str] = pydantic.Field(default=None) + """ + The IP address submitted for authentication + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Status of the IP authentication request + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_accounts_list_response.py b/src/wavix/types/sub_accounts_list_response.py new file mode 100644 index 0000000..894f70f --- /dev/null +++ b/src/wavix/types/sub_accounts_list_response.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .pagination import Pagination +from .sub_organization_response import SubOrganizationResponse + + +class SubAccountsListResponse(UniversalBaseModel): + sub_organizations: typing.Optional[typing.List[SubOrganizationResponse]] = pydantic.Field(default=None) + """ + Subaccounts belonging to the authenticated account. + """ + + pagination: typing.Optional[Pagination] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_accounts_transactions_list_response.py b/src/wavix/types/sub_accounts_transactions_list_response.py new file mode 100644 index 0000000..f495e1c --- /dev/null +++ b/src/wavix/types/sub_accounts_transactions_list_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .pagination import Pagination +from .sub_accounts_transactions_list_response_transactions_item import ( + SubAccountsTransactionsListResponseTransactionsItem, +) + + +class SubAccountsTransactionsListResponse(UniversalBaseModel): + transactions: typing.Optional[typing.List[SubAccountsTransactionsListResponseTransactionsItem]] = pydantic.Field( + default=None + ) + """ + Financial transactions for the subaccount. + """ + + pagination: typing.Optional[Pagination] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_accounts_transactions_list_response_transactions_item.py b/src/wavix/types/sub_accounts_transactions_list_response_transactions_item.py new file mode 100644 index 0000000..f7ca611 --- /dev/null +++ b/src/wavix/types/sub_accounts_transactions_list_response_transactions_item.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SubAccountsTransactionsListResponseTransactionsItem(UniversalBaseModel): + amount: typing.Optional[float] = pydantic.Field(default=None) + """ + Transaction amount. + """ + + balance_after: typing.Optional[float] = pydantic.Field(default=None) + """ + Sub-account balance after the transaction. + """ + + date: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Transaction date and time in ISO 8601 format. + """ + + details: typing.Optional[str] = pydantic.Field(default=None) + """ + Transaction details. + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Transaction status. + """ + + type: typing.Optional[int] = pydantic.Field(default=None) + """ + Transaction type. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_organization_response.py b/src/wavix/types/sub_organization_response.py new file mode 100644 index 0000000..6c15ee0 --- /dev/null +++ b/src/wavix/types/sub_organization_response.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .sub_organization_response_default_destinations import SubOrganizationResponseDefaultDestinations +from .sub_organization_response_status import SubOrganizationResponseStatus + + +class SubOrganizationResponse(UniversalBaseModel): + """ + Sub-account details including API key and webhook configurations. + """ + + id: int = pydantic.Field() + """ + Sub-account ID. + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the sub-account was created in ISO 8601 format. + """ + + name: str = pydantic.Field() + """ + Sub-account name. + """ + + api_key: str = pydantic.Field() + """ + Sub-account API key. + """ + + master_organization: int = pydantic.Field() + """ + Master account ID. + """ + + status: SubOrganizationResponseStatus = pydantic.Field() + """ + Status of the subaccount. One of `enabled` (the subaccount is active and can be used) or `disabled` (the subaccount is suspended). + """ + + default_destinations: SubOrganizationResponseDefaultDestinations = pydantic.Field() + """ + Default webhook URLs for inbound messages and delivery reports. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_organization_response_default_destinations.py b/src/wavix/types/sub_organization_response_default_destinations.py new file mode 100644 index 0000000..d646716 --- /dev/null +++ b/src/wavix/types/sub_organization_response_default_destinations.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SubOrganizationResponseDefaultDestinations(UniversalBaseModel): + """ + Default webhook URLs for inbound messages and delivery reports. + """ + + sms_endpoint: str = pydantic.Field() + """ + Inbound messages webhook URL. + """ + + dlr_endpoint: str = pydantic.Field() + """ + Delivery report webhook URL. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/sub_organization_response_status.py b/src/wavix/types/sub_organization_response_status.py new file mode 100644 index 0000000..8e8dd5c --- /dev/null +++ b/src/wavix/types/sub_organization_response_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SubOrganizationResponseStatus = typing.Union[typing.Literal["enabled", "disabled"], typing.Any] diff --git a/src/wavix/types/submit_file_transcription_response.py b/src/wavix/types/submit_file_transcription_response.py new file mode 100644 index 0000000..233d9b2 --- /dev/null +++ b/src/wavix/types/submit_file_transcription_response.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SubmitFileTranscriptionResponse(UniversalBaseModel): + file: str = pydantic.Field() + """ + Uploaded file name. + """ + + request_id: str = pydantic.Field() + """ + Transcription request ID. + """ + + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/success_response.py b/src/wavix/types/success_response.py new file mode 100644 index 0000000..a48d062 --- /dev/null +++ b/src/wavix/types/success_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SuccessResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/tcr_error_message.py b/src/wavix/types/tcr_error_message.py new file mode 100644 index 0000000..6c78247 --- /dev/null +++ b/src/wavix/types/tcr_error_message.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TcrErrorMessage(UniversalBaseModel): + code: str = pydantic.Field() + """ + TCR error code + """ + + message: str = pydantic.Field() + """ + A human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/tcr_feedback.py b/src/wavix/types/tcr_feedback.py new file mode 100644 index 0000000..a6c9ebe --- /dev/null +++ b/src/wavix/types/tcr_feedback.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .tcr_feedback_category import TcrFeedbackCategory + + +class TcrFeedback(UniversalBaseModel): + category: typing.List[TcrFeedbackCategory] = pydantic.Field() + """ + The feedback category + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/tcr_feedback_category.py b/src/wavix/types/tcr_feedback_category.py new file mode 100644 index 0000000..6d67f44 --- /dev/null +++ b/src/wavix/types/tcr_feedback_category.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .tcr_error_message import TcrErrorMessage + + +class TcrFeedbackCategory(UniversalBaseModel): + """ + TCR returns the feedback per submitted appeal category + """ + + id: str = pydantic.Field() + """ + The submitted appeal category + """ + + display_name: str = pydantic.Field() + """ + The display name of the category + """ + + description: str = pydantic.Field() + """ + The description of the category + """ + + fields: typing.Optional[str] = pydantic.Field(default=None) + """ + An array of Brand attributes + """ + + errors: typing.Optional[typing.List[TcrErrorMessage]] = pydantic.Field(default=None) + """ + An array of verification errors, if any + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand.py b/src/wavix/types/ten_dlc_brand.py new file mode 100644 index 0000000..2fd9e08 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand.py @@ -0,0 +1,143 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_entity_type import TenDlcBrandEntityType +from .ten_dlc_brand_status import TenDlcBrandStatus + + +class TenDlcBrand(UniversalBaseModel): + """ + Represents a 10DLC brand registered for application-to-person messaging. A Brand identifies the business behind one or more messaging campaigns. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand assigned by the registry. + """ + + dba_name: str = pydantic.Field() + """ + Doing-business-as name, or the public-facing brand name. + """ + + company_name: str = pydantic.Field() + """ + Registered legal name of the company that owns the Brand. + """ + + entity_type: TenDlcBrandEntityType = pydantic.Field() + """ + Company entity type. + """ + + vertical: str = pydantic.Field() + """ + Industry vertical the Brand operates in. + """ + + ein_taxid: str = pydantic.Field() + """ + IRS Employer Identification Number (EIN) or other tax ID of the company. + """ + + ein_taxid_country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code where the Tax ID was issued. + """ + + status: TenDlcBrandStatus = pydantic.Field() + """ + Brand identity verification status. + """ + + website: typing.Optional[str] = pydantic.Field(default=None) + """ + Business website URL. + """ + + stock_symbol: typing.Optional[str] = pydantic.Field(default=None) + """ + Stock ticker symbol of the company. Required for publicly traded companies. + """ + + stock_exchange: typing.Optional[str] = pydantic.Field(default=None) + """ + Code of the stock exchange the company is listed on. Required for publicly traded companies. + """ + + first_name: str = pydantic.Field() + """ + Business contact first name. + """ + + last_name: str = pydantic.Field() + """ + Business contact last name. + """ + + phone_number: str = pydantic.Field() + """ + Support contact phone number in E.164 format. + """ + + email: str = pydantic.Field() + """ + Support contact email address. + """ + + street_address: str = pydantic.Field() + """ + Street address of the business. + """ + + city: str = pydantic.Field() + """ + City of the business address. + """ + + state_or_province: typing.Optional[str] = pydantic.Field(default=None) + """ + State or province of the business address. + """ + + country: str = pydantic.Field() + """ + ISO 3166-1 alpha-2 country code of the business address. + """ + + zip: str = pydantic.Field() + """ + ZIP or postal code of the business address. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the identity verification process explaining the current `status`. + """ + + mock: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the Brand is a mock brand for testing. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Brand was created, in ISO 8601 format. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Brand was last updated, in ISO 8601 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_appeal.py b/src/wavix/types/ten_dlc_brand_appeal.py new file mode 100644 index 0000000..3b06f55 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_appeal.py @@ -0,0 +1,54 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_appeal_outcome import TenDlcBrandAppealOutcome + + +class TenDlcBrandAppeal(UniversalBaseModel): + categories: typing.List[str] = pydantic.Field() + """ + A list of Brand Identity status appeal categories associated with the original request + """ + + created_at: dt.datetime = pydantic.Field() + """ + The date and time the appeal request is created + """ + + evidence: typing.List[str] = pydantic.Field() + """ + A list of evidence UUIDs to be associated with the appeal + """ + + outcome: TenDlcBrandAppealOutcome = pydantic.Field() + """ + The appeal outcome details + """ + + status: str = pydantic.Field() + """ + The appeal status + """ + + updated_at: dt.datetime = pydantic.Field() + """ + The date and time the appeal request is updated + """ + + explanation: str = pydantic.Field() + """ + The appeal justification + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_appeal_create_request.py b/src/wavix/types/ten_dlc_brand_appeal_create_request.py new file mode 100644 index 0000000..475985d --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_appeal_create_request.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandAppealCreateRequest(UniversalBaseModel): + """ + Brand identity verification appeal details. + """ + + appeal_categories: typing.List[str] = pydantic.Field() + """ + List of appeal categories. Allowed values: `VERIFY_TAX_ID`, `VERIFY_NON_PROFIT`, `VERIFY_GOVERNMENT` + """ + + evidence: typing.List[str] = pydantic.Field() + """ + List of evidence IDs associated with the appeal. + """ + + explanation: typing.Optional[str] = pydantic.Field(default=None) + """ + Appeal comment or justification. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_appeal_outcome.py b/src/wavix/types/ten_dlc_brand_appeal_outcome.py new file mode 100644 index 0000000..c2cc4ec --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_appeal_outcome.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_appeal_outcome_feedback import TenDlcBrandAppealOutcomeFeedback +from .ten_dlc_brand_appeal_outcome_vetting_status import TenDlcBrandAppealOutcomeVettingStatus + + +class TenDlcBrandAppealOutcome(UniversalBaseModel): + """ + The appeal outcome details + """ + + optional_attributes: typing.Dict[str, typing.Any] = pydantic.Field() + """ + An optional attributes that might be returned from TCR + """ + + vetting_status: TenDlcBrandAppealOutcomeVettingStatus = pydantic.Field() + """ + Brand Identity Verification appeal outcome + """ + + feedback: TenDlcBrandAppealOutcomeFeedback = pydantic.Field() + """ + Brand Identity Verification appeal feedback, if any + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_appeal_outcome_feedback.py b/src/wavix/types/ten_dlc_brand_appeal_outcome_feedback.py new file mode 100644 index 0000000..dcd5346 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_appeal_outcome_feedback.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .tcr_feedback_category import TcrFeedbackCategory + + +class TenDlcBrandAppealOutcomeFeedback(UniversalBaseModel): + """ + Brand Identity Verification appeal feedback, if any + """ + + category: typing.List[TcrFeedbackCategory] = pydantic.Field() + """ + The feedback category + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_appeal_outcome_vetting_status.py b/src/wavix/types/ten_dlc_brand_appeal_outcome_vetting_status.py new file mode 100644 index 0000000..094f262 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_appeal_outcome_vetting_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandAppealOutcomeVettingStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_create_request.py b/src/wavix/types/ten_dlc_brand_create_request.py new file mode 100644 index 0000000..c174fd4 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .ten_dlc_brand_create_request_stock_exchange import TenDlcBrandCreateRequestStockExchange +from .ten_dlc_brand_create_request_zero import TenDlcBrandCreateRequestZero + +TenDlcBrandCreateRequest = typing.Union[TenDlcBrandCreateRequestZero, TenDlcBrandCreateRequestStockExchange] diff --git a/src/wavix/types/ten_dlc_brand_create_request_entity_type.py b/src/wavix/types/ten_dlc_brand_create_request_entity_type.py new file mode 100644 index 0000000..0ffae0f --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandCreateRequestEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_create_request_stock_exchange.py b/src/wavix/types/ten_dlc_brand_create_request_stock_exchange.py new file mode 100644 index 0000000..9537ce1 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_stock_exchange.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_create_request_stock_exchange_entity_type import TenDlcBrandCreateRequestStockExchangeEntityType + + +class TenDlcBrandCreateRequestStockExchange(UniversalBaseModel): + """ + Non-PUBLIC_PROFIT brands must not include stock_symbol or stock_exchange. + """ + + entity_type: typing.Optional[TenDlcBrandCreateRequestStockExchangeEntityType] = None + stock_symbol: typing.Optional[typing.Any] = None + stock_exchange: typing.Optional[typing.Any] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_create_request_stock_exchange_entity_type.py b/src/wavix/types/ten_dlc_brand_create_request_stock_exchange_entity_type.py new file mode 100644 index 0000000..e70c9e0 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_stock_exchange_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandCreateRequestStockExchangeEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_create_request_vertical.py b/src/wavix/types/ten_dlc_brand_create_request_vertical.py new file mode 100644 index 0000000..0375d09 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_vertical.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandCreateRequestVertical = typing.Union[ + typing.Literal[ + "HEALTHCARE", + "PROFESSIONAL", + "RETAIL", + "TECHNOLOGY", + "EDUCATION", + "FINANCIAL", + "NON_PROFIT", + "GOVERNMENT", + "OTHER", + ], + typing.Any, +] diff --git a/src/wavix/types/ten_dlc_brand_create_request_zero.py b/src/wavix/types/ten_dlc_brand_create_request_zero.py new file mode 100644 index 0000000..9cb3ca4 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_zero.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_create_request_zero_entity_type import TenDlcBrandCreateRequestZeroEntityType + + +class TenDlcBrandCreateRequestZero(UniversalBaseModel): + """ + PUBLIC_PROFIT brands must provide stock_symbol and stock_exchange. + """ + + entity_type: typing.Optional[TenDlcBrandCreateRequestZeroEntityType] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_create_request_zero_entity_type.py b/src/wavix/types/ten_dlc_brand_create_request_zero_entity_type.py new file mode 100644 index 0000000..f3a934c --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_create_request_zero_entity_type.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandCreateRequestZeroEntityType = typing.Union[typing.Literal["PUBLIC_PROFIT"], typing.Any] diff --git a/src/wavix/types/ten_dlc_brand_entity_type.py b/src/wavix/types/ten_dlc_brand_entity_type.py new file mode 100644 index 0000000..adbb305 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_entity_type.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandEntityType = typing.Union[ + typing.Literal["PRIVATE_PROFIT", "PUBLIC_PROFIT", "NON_PROFIT", "GOVERNMENT"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_evidence.py b/src/wavix/types/ten_dlc_brand_evidence.py new file mode 100644 index 0000000..3dab764 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_evidence.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class TenDlcBrandEvidence(UniversalBaseModel): + file_name: str = pydantic.Field() + """ + The uploaded file name + """ + + mime_type: str = pydantic.Field() + """ + The uploaded file media type + """ + + url: str = pydantic.Field() + """ + An URL to the uploaded file + """ + + uuid_: typing_extensions.Annotated[ + str, FieldMetadata(alias="uuid"), pydantic.Field(alias="uuid", description="The evidence UUID") + ] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_identity_verification_status.py b/src/wavix/types/ten_dlc_brand_identity_verification_status.py new file mode 100644 index 0000000..3ae4cba --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_identity_verification_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandIdentityVerificationStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_list_response.py b/src/wavix/types/ten_dlc_brand_list_response.py new file mode 100644 index 0000000..0a5eb1e --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_list_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand import TenDlcBrand +from .ten_dlc_brand_list_response_pagination import TenDlcBrandListResponsePagination + + +class TenDlcBrandListResponse(UniversalBaseModel): + """ + A list of 10DLC Brands + """ + + items: typing.List[TenDlcBrand] = pydantic.Field() + """ + A paginated list of 10DLC Brands matching the filter criteria + """ + + pagination: TenDlcBrandListResponsePagination = pydantic.Field() + """ + Pagination details + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_list_response_pagination.py b/src/wavix/types/ten_dlc_brand_list_response_pagination.py new file mode 100644 index 0000000..272949d --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_list_response_pagination.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandListResponsePagination(UniversalBaseModel): + """ + Pagination details + """ + + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_qualification_result.py b/src/wavix/types/ten_dlc_brand_qualification_result.py new file mode 100644 index 0000000..3fba745 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_qualification_result.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlcmno_metadata import TenDlcmnoMetadata + + +class TenDlcBrandQualificationResult(UniversalBaseModel): + mno_metadata: typing.List[TenDlcmnoMetadata] = pydantic.Field() + """ + An array MNO-specific attributes (e.g. AT&T message class) for every MNO the Brand is qualified to run a Campaign with the specified use case. + """ + + monthly_fee: float = pydantic.Field() + """ + Monthly fee associated with any Campaign with this use case + """ + + usecase: str = pydantic.Field() + """ + The use case name + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_status.py b/src/wavix/types/ten_dlc_brand_status.py new file mode 100644 index 0000000..daea288 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_status.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TenDlcBrandStatus = typing.Union[ + typing.Literal["REVIEW", "VERIFIED", "UNVERIFIED", "VETTED_VERIFIED", "SUSPENDED"], typing.Any +] diff --git a/src/wavix/types/ten_dlc_brand_vetting.py b/src/wavix/types/ten_dlc_brand_vetting.py new file mode 100644 index 0000000..07151aa --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandVetting(UniversalBaseModel): + """ + Represents the result of an external vetting performed on a 10DLC brand. Vetting can raise a Brand's trust score and unlock higher messaging throughput. + """ + + evp_id: str = pydantic.Field() + """ + Code identifying the external vetting provider that performed the vetting. + """ + + create_date: str = pydantic.Field() + """ + Timestamp when the vetting request was created, in ISO 8601 format. + """ + + vetting_details: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Additional provider-specific details about the vetting request. + """ + + vetted_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Timestamp when the vetting was completed, in ISO 8601 format. Null while the vetting is still in progress. + """ + + vetting_id: str = pydantic.Field() + """ + Unique identifier of the vetting request. + """ + + vetting_token: typing.Optional[str] = pydantic.Field(default=None) + """ + Token issued by the vetting provider that uniquely identifies this vetting result. + """ + + vetting_score: typing.Optional[int] = pydantic.Field(default=None) + """ + Score assigned to the Brand by the vetting provider. Null until vetting completes. + """ + + vetting_class: str = pydantic.Field() + """ + Class of vetting performed, such as `STANDARD` or `ENHANCED`. + """ + + vetting_status: str = pydantic.Field() + """ + Current status of the vetting request, such as `PENDING` or `ACTIVE`. + """ + + reasons: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Reasons explaining the assigned `vetting_score`. Null when no reasons are provided. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal.py b/src/wavix/types/ten_dlc_brand_vetting_appeal.py new file mode 100644 index 0000000..f9c5f35 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal.py @@ -0,0 +1,78 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_vetting_appeal_appeal_outcome import TenDlcBrandVettingAppealAppealOutcome + + +class TenDlcBrandVettingAppeal(UniversalBaseModel): + """ + Represents an appeal against a 10DLC brand vetting result. An appeal asks the vetting provider to reconsider the assigned score. + """ + + appeal_outcome: TenDlcBrandVettingAppealAppealOutcome = pydantic.Field() + """ + Outcome of the appeal, including the revised vetting status and score. + """ + + appeal_status: str = pydantic.Field() + """ + Current status of the appeal, such as `PENDING` or `COMPLETE`. + """ + + appeal_status_update_date: dt.datetime = pydantic.Field() + """ + Timestamp when the appeal status was last updated, in ISO 8601 format. + """ + + attachment_uuid_list: typing.List[str] = pydantic.Field() + """ + UUIDs of the evidence files submitted in support of the appeal. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand the appeal is associated with. + """ + + category_list: typing.List[str] = pydantic.Field() + """ + Categories that classify the appeal, such as `LOW_SCORE`. + """ + + create_date: str = pydantic.Field() + """ + Timestamp when the appeal was created, in ISO 8601 format. + """ + + explanation: str = pydantic.Field() + """ + Justification provided for the appeal. + """ + + evp_id: str = pydantic.Field() + """ + Code identifying the external vetting provider that handled the appeal. + """ + + vetting_class: str = pydantic.Field() + """ + Class of the vetting being appealed, such as `STANDARD` or `ENHANCED`. + """ + + vetting_id: str = pydantic.Field() + """ + Unique identifier of the vetting request being appealed. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome.py b/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome.py new file mode 100644 index 0000000..f95d944 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_vetting_appeal_appeal_outcome_feedback import TenDlcBrandVettingAppealAppealOutcomeFeedback + + +class TenDlcBrandVettingAppealAppealOutcome(UniversalBaseModel): + """ + Outcome of the appeal, including the revised vetting status and score. + """ + + vet_status: str = pydantic.Field() + """ + Current status of the Brand vetting + """ + + vet_score: int = pydantic.Field() + """ + The Brand vetting score + """ + + feedback: TenDlcBrandVettingAppealAppealOutcomeFeedback = pydantic.Field() + """ + The feedback provided by the external vetting provider + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome_feedback.py b/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome_feedback.py new file mode 100644 index 0000000..5014d8c --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal_appeal_outcome_feedback.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandVettingAppealAppealOutcomeFeedback(UniversalBaseModel): + """ + The feedback provided by the external vetting provider + """ + + reasons: typing.List[str] = pydantic.Field() + """ + An list of human-readable explanations returned by TCR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome.py b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome.py new file mode 100644 index 0000000..65468fe --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_brand_vetting_appeal_outcome_feedback import TenDlcBrandVettingAppealOutcomeFeedback + + +class TenDlcBrandVettingAppealOutcome(UniversalBaseModel): + vet_status: str = pydantic.Field() + """ + Current status of the Brand vetting + """ + + vet_score: int = pydantic.Field() + """ + The Brand vetting score + """ + + feedback: TenDlcBrandVettingAppealOutcomeFeedback = pydantic.Field() + """ + The feedback provided by the external vetting provider + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_feedback.py b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_feedback.py new file mode 100644 index 0000000..78532d8 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_feedback.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandVettingAppealOutcomeFeedback(UniversalBaseModel): + """ + The feedback provided by the external vetting provider + """ + + reasons: typing.List[str] = pydantic.Field() + """ + An list of human-readable explanations returned by TCR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_reason.py b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_reason.py new file mode 100644 index 0000000..ddd14f1 --- /dev/null +++ b/src/wavix/types/ten_dlc_brand_vetting_appeal_outcome_reason.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcBrandVettingAppealOutcomeReason(UniversalBaseModel): + reasons: typing.List[str] = pydantic.Field() + """ + An list of human-readable explanations returned by TCR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign.py b/src/wavix/types/ten_dlc_campaign.py new file mode 100644 index 0000000..6518b7e --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign.py @@ -0,0 +1,211 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcCampaign(UniversalBaseModel): + """ + Represents a 10DLC campaign registered under a Brand. A Campaign defines the messaging use case, opt-in and opt-out flows, and the phone numbers permitted to send its traffic. + """ + + affiliate_marketing: bool = pydantic.Field() + """ + Indicates whether the Campaign is used for affiliate marketing. + """ + + age_gated: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain age-gated content. + """ + + auto_renewal: bool = pydantic.Field() + """ + Indicates whether the Campaign should be automatically renewed. + """ + + last_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign was last billed in ISO 8601 format. `null` if the Campaign has never been billed. + """ + + next_bill_date: typing.Optional[str] = pydantic.Field(default=None) + """ + Date and time the Campaign will be billed next in ISO 8601 format. `null` if the next billing date is not scheduled. + """ + + direct_lending: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain direct lending content. + """ + + embedded_links: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded links. + """ + + embedded_phones: bool = pydantic.Field() + """ + Indicates whether the Campaign messages contain embedded phone numbers. + """ + + embedded_link_sample: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample of an embedded link used in Campaign messages. + """ + + brand_id: str = pydantic.Field() + """ + Unique identifier of the Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the Campaign assigned by the registry. + """ + + description: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the Campaign and its messaging purpose. + """ + + optin_workflow: typing.Optional[str] = pydantic.Field(default=None) + """ + Description of the workflow through which subscribers opt in to the Campaign. + """ + + feedback: typing.Optional[str] = pydantic.Field(default=None) + """ + Feedback from the registry explaining the current `status`. + """ + + help: bool = pydantic.Field() + """ + Indicates whether the campaign includes a help system (for example, keyword: HELP, INFO). + """ + + help_keywords: str = pydantic.Field() + """ + Comma-separated list of help keywords. Keywords are case-insensitive. + """ + + help_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Help message sent upon receiving a help keyword. + """ + + optin: bool = pydantic.Field() + """ + Indicates whether the Campaign requires subscriber opt-in. + """ + + optin_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-in keywords. Keywords are case-insensitive. + """ + + optin_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-in message sent upon receiving an opt-in keyword. + """ + + optout: bool = pydantic.Field() + """ + Indicates whether the campaign includes an opt-out system (for example, keyword: STOP, QUIT). + """ + + optout_keywords: str = pydantic.Field() + """ + Comma-separated list of opt-out keywords. Keywords are case-insensitive. + """ + + optout_message: typing.Optional[str] = pydantic.Field(default=None) + """ + Opt-out message sent upon receiving an opt-out keyword. + """ + + name: str = pydantic.Field() + """ + Display name of the Campaign. + """ + + created_at: str = pydantic.Field() + """ + Timestamp when the Campaign was created, in ISO 8601 format. + """ + + sample1: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample2: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample3: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample4: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + sample5: typing.Optional[str] = pydantic.Field(default=None) + """ + Sample message demonstrating the content sent through the Campaign. + """ + + updated_at: str = pydantic.Field() + """ + Timestamp when the Campaign was last updated, in ISO 8601 format. + """ + + mock: bool = pydantic.Field() + """ + Indicates whether the Campaign is a mock campaign used for testing. Mock campaigns cannot send production traffic. + """ + + usecase: str = pydantic.Field() + """ + Registered use case for the Campaign, such as `2FA` or `MARKETING`. + """ + + monthly_fee: str = pydantic.Field() + """ + Recurring monthly fee charged for the Campaign, as a decimal string. + """ + + privacy_policy: typing.Optional[str] = pydantic.Field(default=None) + """ + Privacy policy URL. + """ + + terms_conditions: typing.Optional[str] = pydantic.Field(default=None) + """ + Terms and conditions URL. + """ + + status: str = pydantic.Field() + """ + Current registration status of the Campaign, such as `APPROVED` or `PENDING`. + """ + + phone_numbers: typing.List[str] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign_list_response.py b/src/wavix/types/ten_dlc_campaign_list_response.py new file mode 100644 index 0000000..13d0ba1 --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign_list_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_campaign import TenDlcCampaign +from .ten_dlc_campaign_list_response_pagination import TenDlcCampaignListResponsePagination + + +class TenDlcCampaignListResponse(UniversalBaseModel): + """ + Paginated list of 10DLC Campaigns. + """ + + items: typing.List[TenDlcCampaign] = pydantic.Field() + """ + 10DLC Campaigns on the current page that match the filter criteria. + """ + + pagination: TenDlcCampaignListResponsePagination = pydantic.Field() + """ + Pagination metadata for the result set. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign_list_response_pagination.py b/src/wavix/types/ten_dlc_campaign_list_response_pagination.py new file mode 100644 index 0000000..2071c0d --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign_list_response_pagination.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcCampaignListResponsePagination(UniversalBaseModel): + """ + Pagination metadata for the result set. + """ + + current_page: int = pydantic.Field() + """ + Current page number. + """ + + per_page: int = pydantic.Field() + """ + Number of records per page. + """ + + total: int = pydantic.Field() + """ + Total number of records. + """ + + total_pages: int = pydantic.Field() + """ + Total number of pages. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign_nudge_request.py b/src/wavix/types/ten_dlc_campaign_nudge_request.py new file mode 100644 index 0000000..f6690ca --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign_nudge_request.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcCampaignNudgeRequest(UniversalBaseModel): + """ + Nudge configuration. Set the nudge intent to `REVIEW` to request action on a pending approval, or to `APPEAL_REJECTION` to submit an appeal for a rejected campaign. + """ + + nudge_intent: str = pydantic.Field() + """ + Nudge intent. Allowed values: `REVIEW`, `APPEAL_REJECTION`. + Use `nudge_intent` to specify the action: - `REVIEW`: Request review for a pending Campaign. - `APPEAL_REJECTION`: Appeal a rejected Campaign. + """ + + description: str = pydantic.Field() + """ + Description of the nudge request. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign_number.py b/src/wavix/types/ten_dlc_campaign_number.py new file mode 100644 index 0000000..52ce9d0 --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign_number.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcCampaignNumber(UniversalBaseModel): + number: str = pydantic.Field() + """ + Phone number assigned to the 10DLC Campaign, in E.164 format. + """ + + status: str = pydantic.Field() + """ + Provisioning status of the phone number, such as `APPROVED`. Only `APPROVED` numbers can be used as Sender IDs. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_campaign_number_list_response.py b/src/wavix/types/ten_dlc_campaign_number_list_response.py new file mode 100644 index 0000000..15c6208 --- /dev/null +++ b/src/wavix/types/ten_dlc_campaign_number_list_response.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .ten_dlc_campaign_number import TenDlcCampaignNumber + + +class TenDlcCampaignNumberListResponse(UniversalBaseModel): + brand_id: str = pydantic.Field() + """ + Unique identifier of the 10DLC Brand that owns the Campaign. + """ + + campaign_id: str = pydantic.Field() + """ + Unique identifier of the 10DLC Campaign the numbers belong to. + """ + + numbers: typing.List[TenDlcCampaignNumber] = pydantic.Field() + """ + Phone numbers assigned to the Campaign, with their provisioning status. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlc_event_subscription.py b/src/wavix/types/ten_dlc_event_subscription.py new file mode 100644 index 0000000..c3b8311 --- /dev/null +++ b/src/wavix/types/ten_dlc_event_subscription.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcEventSubscription(UniversalBaseModel): + """ + Represents a subscription that delivers 10DLC lifecycle events to a webhook URL. + """ + + subscription_category: str = pydantic.Field() + """ + Category of 10DLC events to subscribe to. One of `brand` (brand status changes), `campaign` (campaign status changes), or `number` (number provisioning changes). + """ + + url: str = pydantic.Field() + """ + Webhook URL that events in this category are delivered to. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/ten_dlcmno_metadata.py b/src/wavix/types/ten_dlcmno_metadata.py new file mode 100644 index 0000000..da81f3f --- /dev/null +++ b/src/wavix/types/ten_dlcmno_metadata.py @@ -0,0 +1,101 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TenDlcmnoMetadata(UniversalBaseModel): + """ + Represents per-MNO 10DLC requirements and throughput limits that apply to a campaign use case. + """ + + att_mms_tpm: typing.Optional[int] = pydantic.Field(default=None) + """ + MMS throughput per minute allowed on the AT&T network. + """ + + att_msg_class: typing.Optional[str] = pydantic.Field(default=None) + """ + Message class assigned by AT&T. + """ + + att_sms_tpm: typing.Optional[int] = pydantic.Field(default=None) + """ + SMS throughput per minute allowed on the AT&T network. + """ + + att_tpm_scope: typing.Optional[str] = pydantic.Field(default=None) + """ + Scope of the AT&T throughput-per-minute allocation. + """ + + help_required: bool = pydantic.Field() + """ + Indicates whether help keywords and an acknowledgement are mandatory for the use case. + """ + + optin_required: bool = pydantic.Field() + """ + Indicates whether an opt-in mechanism is mandatory for the use case. + """ + + optout_required: bool = pydantic.Field() + """ + Indicates whether an opt-out mechanism is mandatory for the use case. + """ + + min_msg_samples: int = pydantic.Field() + """ + Minimum number of message samples the MNO requires for the use case. + """ + + mno: str = pydantic.Field() + """ + Name of the mobile network operator (MNO) these requirements apply to. + """ + + mno_qualify: bool = pydantic.Field() + """ + Indicates whether the Brand qualifies with the MNO for the selected use case. + """ + + mno_review: bool = pydantic.Field() + """ + Indicates whether the MNO requires a post-approval review of the use case. + """ + + mno_support: bool = pydantic.Field() + """ + Indicates whether the MNO supports the use case. + """ + + no_embedded_links: bool = pydantic.Field() + """ + Indicates whether embedded links are prohibited in message content for the use case. + """ + + no_embedded_phone: bool = pydantic.Field() + """ + Indicates whether embedded phone numbers are prohibited in message content for the use case. + """ + + tmobile_brand_dcap: typing.Optional[int] = pydantic.Field(default=None) + """ + Daily message cap allowed on the T-Mobile network. + """ + + tmobile_brand_tier: typing.Optional[str] = pydantic.Field(default=None) + """ + Brand tier assigned on the T-Mobile network, such as `LOW`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transaction_status.py b/src/wavix/types/transaction_status.py new file mode 100644 index 0000000..153625c --- /dev/null +++ b/src/wavix/types/transaction_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TransactionStatus = typing.Union[typing.Literal["Created", "Pending", "Committed", "Reverted"], typing.Any] diff --git a/src/wavix/types/transaction_type.py b/src/wavix/types/transaction_type.py new file mode 100644 index 0000000..d9058d0 --- /dev/null +++ b/src/wavix/types/transaction_type.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +TransactionType = int +""" +Transaction type: + * 0 - Payment adjustment + * 2 - Phone number activation fee + * 3 - Phone number monthly fees + * 6 - Phone number forwarding + * 11 - PSTN forwarding fee + * 14 - Outbound call + * 15 - Outbound SMS + * 19 - Credit card payment + * 20 - Payment fee + * 23 - Porting fee + * 24 - Inbound SMS + * 25 - Admin payment + * 26 - Subscription payment + * 29 - Number validator + * 30 - Call recording + * 31 - Storage charge + * 32 - Campaign builder + * 33 - Voicemail detection + * 34 - Sender ID registration + * 35 - Sender ID monthly fee + * 36 - 2FA + * 37 - IVR + * 38 - E911 activation + * 39 - Outbound MMS + * 40 - Inbound MMS + * 41 - Call transcription + * 42 - 10DLC Brand registration + * 43 - 10DLC Campaign registration + * 44 - Phone number order + * 45 - Adjustment in + * 46 - URL shortener + * 47 - 10DLC Brand update + * 48 - 10DLC Brand appeal + * 49 - Audio transcription + * 50 - 10DLC Brand vetting + * 51 - 10DLC Brand vetting appeal + * 52 - Outbound SMS carrier fee + * 53 - Inbound SMS carrier fee + * 54 - Outbound MMS carrier fee + * 55 - Inbound MMS carrier fee + * 57 - Outbound SMS segment + * 58 - Inbound SMS segment + * 59 - Outbound MMS segment + * 60 - Inbound MMS segment +""" diff --git a/src/wavix/types/transcript_turn.py b/src/wavix/types/transcript_turn.py new file mode 100644 index 0000000..65a3131 --- /dev/null +++ b/src/wavix/types/transcript_turn.py @@ -0,0 +1,46 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TranscriptTurn(UniversalBaseModel): + """ + Represents a single turn in a call transcript. Each turn carries the text attributed to one speaker, with the start and end times for that text. + """ + + type: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number of the speaker attributed to this turn, in E.164 format. + """ + + s: typing.Optional[int] = pydantic.Field(default=None) + """ + Start of the `turn`, in milliseconds. The start time is calculated from the moment the call was answered. + """ + + e: typing.Optional[int] = pydantic.Field(default=None) + """ + End of the `turn`, in milliseconds. The end time is calculated from the moment the call was answered. + """ + + text: typing.Optional[str] = pydantic.Field(default=None) + """ + Transcribed text attributed to the speaker for this turn. + """ + + sentiment: typing.Optional[str] = pydantic.Field(default=None) + """ + Sentiment detected in the speaker's text for this turn, such as `neutral`, `positive`, or `negative`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_filter.py b/src/wavix/types/transcription_filter.py new file mode 100644 index 0000000..92c8560 --- /dev/null +++ b/src/wavix/types/transcription_filter.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .transcription_filter_agent import TranscriptionFilterAgent +from .transcription_filter_any import TranscriptionFilterAny +from .transcription_filter_client import TranscriptionFilterClient + + +class TranscriptionFilter(UniversalBaseModel): + agent: TranscriptionFilterAgent = pydantic.Field() + """ + Search in an agent's spoken words and phrases + """ + + client: TranscriptionFilterClient = pydantic.Field() + """ + Search in an customer's spoken words and phrases + """ + + any: TranscriptionFilterAny = pydantic.Field() + """ + Search in both speakers' spoken words and phrases + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_filter_agent.py b/src/wavix/types/transcription_filter_agent.py new file mode 100644 index 0000000..f320613 --- /dev/null +++ b/src/wavix/types/transcription_filter_agent.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TranscriptionFilterAgent(UniversalBaseModel): + """ + Search in an agent's spoken words and phrases + """ + + must: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes all of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical AND + """ + + match: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + exclude: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that does not include any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_filter_any.py b/src/wavix/types/transcription_filter_any.py new file mode 100644 index 0000000..d93b958 --- /dev/null +++ b/src/wavix/types/transcription_filter_any.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TranscriptionFilterAny(UniversalBaseModel): + """ + Search in both speakers' spoken words and phrases + """ + + must: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes all of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical AND + """ + + match: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + exclude: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that does not include any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_filter_client.py b/src/wavix/types/transcription_filter_client.py new file mode 100644 index 0000000..e4012ff --- /dev/null +++ b/src/wavix/types/transcription_filter_client.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TranscriptionFilterClient(UniversalBaseModel): + """ + Search in an customer's spoken words and phrases + """ + + must: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes all of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical AND + """ + + match: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that includes any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + exclude: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Only calls with transcription that does not include any of the specified keywords and phrases are returned. The listed keywords and phrases are combined using logical OR + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_language.py b/src/wavix/types/transcription_language.py new file mode 100644 index 0000000..7a7a68a --- /dev/null +++ b/src/wavix/types/transcription_language.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionLanguage = typing.Union[typing.Literal["en", "de", "es", "fr", "it"], typing.Any] diff --git a/src/wavix/types/transcription_reference.py b/src/wavix/types/transcription_reference.py new file mode 100644 index 0000000..d114aec --- /dev/null +++ b/src/wavix/types/transcription_reference.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class TranscriptionReference(UniversalBaseModel): + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Unique identifier of the call transcription."), + ] + url: str = pydantic.Field() + """ + URL for retrieving the full call transcription. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/transcription_status.py b/src/wavix/types/transcription_status.py new file mode 100644 index 0000000..534b7c9 --- /dev/null +++ b/src/wavix/types/transcription_status.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TranscriptionStatus = typing.Union[typing.Literal["completed", "failed"], typing.Any] diff --git a/src/wavix/types/tts_language.py b/src/wavix/types/tts_language.py new file mode 100644 index 0000000..7096e61 --- /dev/null +++ b/src/wavix/types/tts_language.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TtsLanguage = typing.Union[typing.Literal["ru", "en", "sp", "ge"], typing.Any] diff --git a/src/wavix/types/tts_voice_id.py b/src/wavix/types/tts_voice_id.py new file mode 100644 index 0000000..e5c96c7 --- /dev/null +++ b/src/wavix/types/tts_voice_id.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +TtsVoiceId = typing.Union[ + typing.Literal[ + "Ivy", + "Joanna", + "Kendra", + "Kimberly", + "Salli", + "Joey", + "Justin", + "Matthew", + "Conchita", + "Lucia", + "Enrique", + "Marlene", + "Vicki", + "Hans", + "Tatyana", + "Maxim", + ], + typing.Any, +] diff --git a/src/wavix/types/two_factor_verification_check_response.py b/src/wavix/types/two_factor_verification_check_response.py new file mode 100644 index 0000000..6a51fab --- /dev/null +++ b/src/wavix/types/two_factor_verification_check_response.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TwoFactorVerificationCheckResponse(UniversalBaseModel): + is_valid: bool = pydantic.Field() + """ + Indicates whether the entered code is valid + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/two_factor_verification_event.py b/src/wavix/types/two_factor_verification_event.py new file mode 100644 index 0000000..894cf54 --- /dev/null +++ b/src/wavix/types/two_factor_verification_event.py @@ -0,0 +1,47 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TwoFactorVerificationEvent(UniversalBaseModel): + created_at: dt.datetime = pydantic.Field() + """ + Date and time of the event + """ + + event: str = pydantic.Field() + """ + Human-readable event description. One of the following values: + - `Number lookup` — the Wavix platform checked whether the destination phone number is valid. Returned only when number validation is enabled for the 2FA Service. + - `Code sent via SMS` — a code was sent via SMS. + - `Code sent via voice` — a code was sent via a voice call. + - `Verification` — a code verification attempt. + """ + + status: str = pydantic.Field() + """ + Status of an action associated with the event. Can be either `success`, `failed`, or `pending`. + """ + + charge: str = pydantic.Field() + """ + Cost of an operation associated with the event, in USD + """ + + error: typing.Optional[str] = pydantic.Field(default=None) + """ + Error description, if any + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/two_factor_verification_resend_response.py b/src/wavix/types/two_factor_verification_resend_response.py new file mode 100644 index 0000000..2f1a607 --- /dev/null +++ b/src/wavix/types/two_factor_verification_resend_response.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class TwoFactorVerificationResendResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the verification code was successfully sent + """ + + channel: str = pydantic.Field() + """ + Indicates whether the code was sent via an SMS or a voice call + """ + + destination: str = pydantic.Field() + """ + The destination phone number the code was sent to + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the code was sent + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/two_factor_verification_response.py b/src/wavix/types/two_factor_verification_response.py new file mode 100644 index 0000000..815e02f --- /dev/null +++ b/src/wavix/types/two_factor_verification_response.py @@ -0,0 +1,51 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .phone_lookup_details import PhoneLookupDetails + + +class TwoFactorVerificationResponse(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the 2FA Verification was successfully created + """ + + service_id: str = pydantic.Field() + """ + Unique identifier of the Wavix 2FA Service + """ + + session_url: str = pydantic.Field() + """ + Automatically generated 2FA Verification URL. The URL can be used to resend or validate the OTP. + """ + + session_id: str = pydantic.Field() + """ + Unique identifier of the Wavix 2FA Verification + """ + + destination: str = pydantic.Field() + """ + The end user's phone number. + """ + + created_at: dt.datetime = pydantic.Field() + """ + Date and time the 2FA Verification is created + """ + + number_lookup: PhoneLookupDetails + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/unauthorized_error_response.py b/src/wavix/types/unauthorized_error_response.py new file mode 100644 index 0000000..e506cf4 --- /dev/null +++ b/src/wavix/types/unauthorized_error_response.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UnauthorizedErrorResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + error: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates that the response represents an error. Always `true` for this error. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable description stating that authentication is missing or invalid. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/unprocessable_entity_error_body.py b/src/wavix/types/unprocessable_entity_error_body.py new file mode 100644 index 0000000..f61c539 --- /dev/null +++ b/src/wavix/types/unprocessable_entity_error_body.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UnprocessableEntityErrorBody(UniversalBaseModel): + success: bool = pydantic.Field() + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: str = pydantic.Field() + """ + Human-readable error description + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/validation_error_response.py b/src/wavix/types/validation_error_response.py new file mode 100644 index 0000000..dd9307b --- /dev/null +++ b/src/wavix/types/validation_error_response.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ValidationErrorResponse(UniversalBaseModel): + success: typing.Optional[bool] = pydantic.Field(default=None) + """ + Indicates whether the request was successful. Always `false` for this error. + """ + + message: typing.Optional[str] = pydantic.Field(default=None) + """ + Human-readable description naming the missing or invalid request parameter. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaign_create_request.py b/src/wavix/types/voice_campaign_create_request.py new file mode 100644 index 0000000..863cefb --- /dev/null +++ b/src/wavix/types/voice_campaign_create_request.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .voice_campaign_response import VoiceCampaignResponse + + +class VoiceCampaignCreateRequest(UniversalBaseModel): + voice_campaign: VoiceCampaignResponse + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaign_response.py b/src/wavix/types/voice_campaign_response.py new file mode 100644 index 0000000..f9134a2 --- /dev/null +++ b/src/wavix/types/voice_campaign_response.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class VoiceCampaignResponse(UniversalBaseModel): + """ + Represents a voice campaign that places an outbound call running a pre-approved call flow. + """ + + callflow_id: int = pydantic.Field() + """ + Unique identifier of the call flow to launch, listed on the Call flows page. Every scenario must be pre-approved by the Wavix Service Operations team before it can be used in production. + """ + + caller_id: str = pydantic.Field() + """ + Phone number on the account used as the Caller ID for the outbound call, in E.164 format. + """ + + contact: str = pydantic.Field() + """ + Destination phone number the outbound call is placed to, in E.164 format. + """ + + callback_url: typing.Optional[str] = pydantic.Field(default=None) + """ + Webhook URL that receives voice campaign status updates. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaigns_create_response.py b/src/wavix/types/voice_campaigns_create_response.py new file mode 100644 index 0000000..dbef373 --- /dev/null +++ b/src/wavix/types/voice_campaigns_create_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .voice_campaigns_create_response_voice_campaign import VoiceCampaignsCreateResponseVoiceCampaign + + +class VoiceCampaignsCreateResponse(UniversalBaseModel): + voice_campaign: typing.Optional[VoiceCampaignsCreateResponseVoiceCampaign] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaigns_create_response_voice_campaign.py b/src/wavix/types/voice_campaigns_create_response_voice_campaign.py new file mode 100644 index 0000000..c58336e --- /dev/null +++ b/src/wavix/types/voice_campaigns_create_response_voice_campaign.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class VoiceCampaignsCreateResponseVoiceCampaign(UniversalBaseModel): + id: typing.Optional[int] = pydantic.Field(default=None) + """ + Unique identifier of the voice campaign. + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Current status of the voice campaign, such as `in_progress`. + """ + + timestamp: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Timestamp when the voice campaign was created, in ISO 8601 format. + """ + + caller_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number used as the Caller ID for the outbound call, in E.164 format. + """ + + contact: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination phone number the outbound call is placed to, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaigns_get_response.py b/src/wavix/types/voice_campaigns_get_response.py new file mode 100644 index 0000000..e4883f2 --- /dev/null +++ b/src/wavix/types/voice_campaigns_get_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .voice_campaigns_get_response_voice_campaign import VoiceCampaignsGetResponseVoiceCampaign + + +class VoiceCampaignsGetResponse(UniversalBaseModel): + voice_campaign: typing.Optional[VoiceCampaignsGetResponseVoiceCampaign] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/voice_campaigns_get_response_voice_campaign.py b/src/wavix/types/voice_campaigns_get_response_voice_campaign.py new file mode 100644 index 0000000..7fe1330 --- /dev/null +++ b/src/wavix/types/voice_campaigns_get_response_voice_campaign.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class VoiceCampaignsGetResponseVoiceCampaign(UniversalBaseModel): + id: typing.Optional[int] = pydantic.Field(default=None) + """ + Unique identifier of the voice campaign. + """ + + status: typing.Optional[str] = pydantic.Field(default=None) + """ + Current status of the voice campaign, such as `in_progress`. + """ + + timestamp: typing.Optional[dt.datetime] = pydantic.Field(default=None) + """ + Timestamp when the voice campaign was created, in ISO 8601 format. + """ + + caller_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Phone number used as the Caller ID for the outbound call, in E.164 format. + """ + + contact: typing.Optional[str] = pydantic.Field(default=None) + """ + Destination phone number the outbound call is placed to, in E.164 format. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/web_rtc_token.py b/src/wavix/types/web_rtc_token.py new file mode 100644 index 0000000..b6c94bb --- /dev/null +++ b/src/wavix/types/web_rtc_token.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class WebRtcToken(UniversalBaseModel): + """ + Represents a WebRTC token that authorizes the Wavix embeddable widget to register against a SIP trunk. + """ + + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Unique identifier of the WebRTC token."), + ] + sip_trunk: str = pydantic.Field() + """ + Name of the SIP trunk the token authenticates against. + """ + + payload: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Arbitrary client-defined data associated with the token. + """ + + ttl: typing.Optional[int] = pydantic.Field(default=None) + """ + Time to live, in seconds. `null` means no expiration. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/web_rtc_token_response.py b/src/wavix/types/web_rtc_token_response.py new file mode 100644 index 0000000..a36555b --- /dev/null +++ b/src/wavix/types/web_rtc_token_response.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class WebRtcTokenResponse(UniversalBaseModel): + token: str = pydantic.Field() + """ + Signed JWT used by the Wavix embeddable widget to authenticate. + """ + + uuid_: typing_extensions.Annotated[ + str, + FieldMetadata(alias="uuid"), + pydantic.Field(alias="uuid", description="Unique identifier of the WebRTC token."), + ] + sip_trunk: str = pydantic.Field() + """ + Name of the SIP trunk the token authenticates against. + """ + + payload: typing.Optional[typing.Dict[str, typing.Any]] = pydantic.Field(default=None) + """ + Arbitrary client-defined data associated with the token. + """ + + ttl: typing.Optional[int] = pydantic.Field(default=None) + """ + Lifetime of the token, in seconds. `null` means the token does not expire. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/types/web_rtc_tokens_list_response.py b/src/wavix/types/web_rtc_tokens_list_response.py new file mode 100644 index 0000000..d417cb0 --- /dev/null +++ b/src/wavix/types/web_rtc_tokens_list_response.py @@ -0,0 +1,30 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .pagination import Pagination +from .web_rtc_token import WebRtcToken + + +class WebRtcTokensListResponse(UniversalBaseModel): + """ + Paginated list of WebRTC tokens. + """ + + items: typing.Optional[typing.List[WebRtcToken]] = pydantic.Field(default=None) + """ + WebRTC tokens on the current page. + """ + + pagination: typing.Optional[Pagination] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/wavix/version.py b/src/wavix/version.py new file mode 100644 index 0000000..8ca8a69 --- /dev/null +++ b/src/wavix/version.py @@ -0,0 +1,3 @@ +from importlib import metadata + +__version__ = metadata.version("wavix-python-sdk") diff --git a/src/wavix/voice_campaigns/__init__.py b/src/wavix/voice_campaigns/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/voice_campaigns/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/voice_campaigns/client.py b/src/wavix/voice_campaigns/client.py new file mode 100644 index 0000000..e896ca3 --- /dev/null +++ b/src/wavix/voice_campaigns/client.py @@ -0,0 +1,197 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.voice_campaign_response import VoiceCampaignResponse +from ..types.voice_campaigns_create_response import VoiceCampaignsCreateResponse +from ..types.voice_campaigns_get_response import VoiceCampaignsGetResponse +from .raw_client import AsyncRawVoiceCampaignsClient, RawVoiceCampaignsClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class VoiceCampaignsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawVoiceCampaignsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawVoiceCampaignsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawVoiceCampaignsClient + """ + return self._raw_client + + def create( + self, *, voice_campaign: VoiceCampaignResponse, request_options: typing.Optional[RequestOptions] = None + ) -> VoiceCampaignsCreateResponse: + """ + Launches a voice campaign that places an outbound call using a pre-configured scenario. Track progress with the returned voice campaign `id`. + + Parameters + ---------- + voice_campaign : VoiceCampaignResponse + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VoiceCampaignsCreateResponse + Returns the launched voice campaign. + + Examples + -------- + from wavix import VoiceCampaignResponse, Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.voice_campaigns.create( + voice_campaign=VoiceCampaignResponse( + callflow_id=3212, + caller_id="13123310912", + contact="16729923812", + ), + ) + """ + _response = self._raw_client.create(voice_campaign=voice_campaign, request_options=request_options) + return _response.data + + def get(self, id: int, *, request_options: typing.Optional[RequestOptions] = None) -> VoiceCampaignsGetResponse: + """ + Returns the voice campaign identified by `id`, including its current status. + + Parameters + ---------- + id : int + The unique ID of the voice campaign to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VoiceCampaignsGetResponse + Returns the voice campaign. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.voice_campaigns.get( + id=2321423, + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + +class AsyncVoiceCampaignsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawVoiceCampaignsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawVoiceCampaignsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawVoiceCampaignsClient + """ + return self._raw_client + + async def create( + self, *, voice_campaign: VoiceCampaignResponse, request_options: typing.Optional[RequestOptions] = None + ) -> VoiceCampaignsCreateResponse: + """ + Launches a voice campaign that places an outbound call using a pre-configured scenario. Track progress with the returned voice campaign `id`. + + Parameters + ---------- + voice_campaign : VoiceCampaignResponse + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VoiceCampaignsCreateResponse + Returns the launched voice campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix, VoiceCampaignResponse + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.voice_campaigns.create( + voice_campaign=VoiceCampaignResponse( + callflow_id=3212, + caller_id="13123310912", + contact="16729923812", + ), + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create(voice_campaign=voice_campaign, request_options=request_options) + return _response.data + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> VoiceCampaignsGetResponse: + """ + Returns the voice campaign identified by `id`, including its current status. + + Parameters + ---------- + id : int + The unique ID of the voice campaign to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + VoiceCampaignsGetResponse + Returns the voice campaign. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.voice_campaigns.get( + id=2321423, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data diff --git a/src/wavix/voice_campaigns/raw_client.py b/src/wavix/voice_campaigns/raw_client.py new file mode 100644 index 0000000..45a73a7 --- /dev/null +++ b/src/wavix/voice_campaigns/raw_client.py @@ -0,0 +1,332 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.jsonable_encoder import encode_path_param +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..core.serialization import convert_and_respect_annotation_metadata +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.not_found_error import NotFoundError +from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.voice_campaign_response import VoiceCampaignResponse +from ..types.voice_campaigns_create_response import VoiceCampaignsCreateResponse +from ..types.voice_campaigns_get_response import VoiceCampaignsGetResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawVoiceCampaignsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, *, voice_campaign: VoiceCampaignResponse, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[VoiceCampaignsCreateResponse]: + """ + Launches a voice campaign that places an outbound call using a pre-configured scenario. Track progress with the returned voice campaign `id`. + + Parameters + ---------- + voice_campaign : VoiceCampaignResponse + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[VoiceCampaignsCreateResponse] + Returns the launched voice campaign. + """ + _response = self._client_wrapper.httpx_client.request( + "v1/voice-campaigns", + method="POST", + json={ + "voice_campaign": convert_and_respect_annotation_metadata( + object_=voice_campaign, annotation=VoiceCampaignResponse, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VoiceCampaignsCreateResponse, + parse_obj_as( + type_=VoiceCampaignsCreateResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[VoiceCampaignsGetResponse]: + """ + Returns the voice campaign identified by `id`, including its current status. + + Parameters + ---------- + id : int + The unique ID of the voice campaign to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[VoiceCampaignsGetResponse] + Returns the voice campaign. + """ + _response = self._client_wrapper.httpx_client.request( + f"v1/voice-campaigns/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VoiceCampaignsGetResponse, + parse_obj_as( + type_=VoiceCampaignsGetResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawVoiceCampaignsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, *, voice_campaign: VoiceCampaignResponse, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[VoiceCampaignsCreateResponse]: + """ + Launches a voice campaign that places an outbound call using a pre-configured scenario. Track progress with the returned voice campaign `id`. + + Parameters + ---------- + voice_campaign : VoiceCampaignResponse + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[VoiceCampaignsCreateResponse] + Returns the launched voice campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + "v1/voice-campaigns", + method="POST", + json={ + "voice_campaign": convert_and_respect_annotation_metadata( + object_=voice_campaign, annotation=VoiceCampaignResponse, direction="write" + ), + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VoiceCampaignsCreateResponse, + parse_obj_as( + type_=VoiceCampaignsCreateResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 422: + raise UnprocessableEntityError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: int, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[VoiceCampaignsGetResponse]: + """ + Returns the voice campaign identified by `id`, including its current status. + + Parameters + ---------- + id : int + The unique ID of the voice campaign to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[VoiceCampaignsGetResponse] + Returns the voice campaign. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v1/voice-campaigns/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + VoiceCampaignsGetResponse, + parse_obj_as( + type_=VoiceCampaignsGetResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/wavix/webrtc/__init__.py b/src/wavix/webrtc/__init__.py new file mode 100644 index 0000000..d3f2a03 --- /dev/null +++ b/src/wavix/webrtc/__init__.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from . import tokens +_dynamic_imports: typing.Dict[str, str] = {"tokens": ".tokens"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["tokens"] diff --git a/src/wavix/webrtc/client.py b/src/wavix/webrtc/client.py new file mode 100644 index 0000000..39c30ee --- /dev/null +++ b/src/wavix/webrtc/client.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .raw_client import AsyncRawWebrtcClient, RawWebrtcClient + +if typing.TYPE_CHECKING: + from .tokens.client import AsyncTokensClient, TokensClient + + +class WebrtcClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawWebrtcClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._tokens: typing.Optional[TokensClient] = None + + @property + def with_raw_response(self) -> RawWebrtcClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawWebrtcClient + """ + return self._raw_client + + @property + def tokens(self): + if self._tokens is None: + from .tokens.client import TokensClient # noqa: E402 + + self._tokens = TokensClient(client_wrapper=self._client_wrapper) + return self._tokens + + +class AsyncWebrtcClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawWebrtcClient(client_wrapper=client_wrapper) + self._client_wrapper = client_wrapper + self._tokens: typing.Optional[AsyncTokensClient] = None + + @property + def with_raw_response(self) -> AsyncRawWebrtcClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawWebrtcClient + """ + return self._raw_client + + @property + def tokens(self): + if self._tokens is None: + from .tokens.client import AsyncTokensClient # noqa: E402 + + self._tokens = AsyncTokensClient(client_wrapper=self._client_wrapper) + return self._tokens diff --git a/src/wavix/webrtc/raw_client.py b/src/wavix/webrtc/raw_client.py new file mode 100644 index 0000000..6ab7d46 --- /dev/null +++ b/src/wavix/webrtc/raw_client.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper + + +class RawWebrtcClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + +class AsyncRawWebrtcClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper diff --git a/src/wavix/webrtc/tokens/__init__.py b/src/wavix/webrtc/tokens/__init__.py new file mode 100644 index 0000000..5cde020 --- /dev/null +++ b/src/wavix/webrtc/tokens/__init__.py @@ -0,0 +1,4 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + diff --git a/src/wavix/webrtc/tokens/client.py b/src/wavix/webrtc/tokens/client.py new file mode 100644 index 0000000..f78747e --- /dev/null +++ b/src/wavix/webrtc/tokens/client.py @@ -0,0 +1,432 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from ...types.success_response import SuccessResponse +from ...types.web_rtc_token import WebRtcToken +from ...types.web_rtc_token_response import WebRtcTokenResponse +from ...types.web_rtc_tokens_list_response import WebRtcTokensListResponse +from .raw_client import AsyncRawTokensClient, RawTokensClient + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class TokensClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawTokensClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawTokensClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawTokensClient + """ + return self._raw_client + + def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> WebRtcTokensListResponse: + """ + Returns a paginated list of active Wavix Embeddable widget tokens for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcTokensListResponse + Returns a paginated list of active widget tokens. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.webrtc.tokens.list() + """ + _response = self._raw_client.list(request_options=request_options) + return _response.data + + def create( + self, + *, + sip_trunk: str, + payload: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + ttl: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> WebRtcTokenResponse: + """ + Creates a Wavix Embeddable widget token that authenticates a browser-based softphone session. The token expires after `ttl` seconds. + + Parameters + ---------- + sip_trunk : str + Name of the SIP trunk the token authenticates against. + + payload : typing.Optional[typing.Dict[str, typing.Any]] + Arbitrary client-defined data to associate with the token. + + ttl : typing.Optional[int] + Time to live in seconds. Pass `null` for no expiration. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcTokenResponse + Returns the created widget token. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.webrtc.tokens.create( + sip_trunk="my-sip-trunk", + payload={"user_id": "42"}, + ttl=3600, + ) + """ + _response = self._raw_client.create( + sip_trunk=sip_trunk, payload=payload, ttl=ttl, request_options=request_options + ) + return _response.data + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> WebRtcToken: + """ + Returns the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcToken + Returns the widget token. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.webrtc.tokens.get( + id="id", + ) + """ + _response = self._raw_client.get(id, request_options=request_options) + return _response.data + + def update( + self, id: str, *, payload: typing.Dict[str, typing.Any], request_options: typing.Optional[RequestOptions] = None + ) -> WebRtcToken: + """ + Updates the `payload` carried by the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to update. + + payload : typing.Dict[str, typing.Any] + Arbitrary client-defined data to associate with the token, replacing the existing payload. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcToken + Returns the updated widget token. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.webrtc.tokens.update( + id="id", + payload={"key": "value"}, + ) + """ + _response = self._raw_client.update(id, payload=payload, request_options=request_options) + return _response.data + + def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the Wavix Embeddable widget token identified by `id`. The token can no longer authenticate widget sessions, and any active session using it ends. + + Parameters + ---------- + id : str + The UUID of the widget token to delete. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The widget token is deleted. + + Examples + -------- + from wavix import Wavix + + client = Wavix( + token="YOUR_TOKEN", + ) + client.webrtc.tokens.delete( + id="id", + ) + """ + _response = self._raw_client.delete(id, request_options=request_options) + return _response.data + + +class AsyncTokensClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawTokensClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawTokensClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawTokensClient + """ + return self._raw_client + + async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> WebRtcTokensListResponse: + """ + Returns a paginated list of active Wavix Embeddable widget tokens for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcTokensListResponse + Returns a paginated list of active widget tokens. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.webrtc.tokens.list() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list(request_options=request_options) + return _response.data + + async def create( + self, + *, + sip_trunk: str, + payload: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + ttl: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> WebRtcTokenResponse: + """ + Creates a Wavix Embeddable widget token that authenticates a browser-based softphone session. The token expires after `ttl` seconds. + + Parameters + ---------- + sip_trunk : str + Name of the SIP trunk the token authenticates against. + + payload : typing.Optional[typing.Dict[str, typing.Any]] + Arbitrary client-defined data to associate with the token. + + ttl : typing.Optional[int] + Time to live in seconds. Pass `null` for no expiration. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcTokenResponse + Returns the created widget token. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.webrtc.tokens.create( + sip_trunk="my-sip-trunk", + payload={"user_id": "42"}, + ttl=3600, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + sip_trunk=sip_trunk, payload=payload, ttl=ttl, request_options=request_options + ) + return _response.data + + async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> WebRtcToken: + """ + Returns the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcToken + Returns the widget token. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.webrtc.tokens.get( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get(id, request_options=request_options) + return _response.data + + async def update( + self, id: str, *, payload: typing.Dict[str, typing.Any], request_options: typing.Optional[RequestOptions] = None + ) -> WebRtcToken: + """ + Updates the `payload` carried by the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to update. + + payload : typing.Dict[str, typing.Any] + Arbitrary client-defined data to associate with the token, replacing the existing payload. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + WebRtcToken + Returns the updated widget token. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.webrtc.tokens.update( + id="id", + payload={"key": "value"}, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.update(id, payload=payload, request_options=request_options) + return _response.data + + async def delete(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SuccessResponse: + """ + Deletes the Wavix Embeddable widget token identified by `id`. The token can no longer authenticate widget sessions, and any active session using it ends. + + Parameters + ---------- + id : str + The UUID of the widget token to delete. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SuccessResponse + Returns a success confirmation. The widget token is deleted. + + Examples + -------- + import asyncio + + from wavix import AsyncWavix + + client = AsyncWavix( + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.webrtc.tokens.delete( + id="id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.delete(id, request_options=request_options) + return _response.data diff --git a/src/wavix/webrtc/tokens/raw_client.py b/src/wavix/webrtc/tokens/raw_client.py new file mode 100644 index 0000000..d5169eb --- /dev/null +++ b/src/wavix/webrtc/tokens/raw_client.py @@ -0,0 +1,869 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.jsonable_encoder import encode_path_param +from ...core.parse_error import ParsingError +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...errors.bad_request_error import BadRequestError +from ...errors.forbidden_error import ForbiddenError +from ...errors.not_found_error import NotFoundError +from ...errors.unauthorized_error import UnauthorizedError +from ...types.success_response import SuccessResponse +from ...types.unauthorized_error_response import UnauthorizedErrorResponse +from ...types.web_rtc_token import WebRtcToken +from ...types.web_rtc_token_response import WebRtcTokenResponse +from ...types.web_rtc_tokens_list_response import WebRtcTokensListResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawTokensClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WebRtcTokensListResponse]: + """ + Returns a paginated list of active Wavix Embeddable widget tokens for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WebRtcTokensListResponse] + Returns a paginated list of active widget tokens. + """ + _response = self._client_wrapper.httpx_client.request( + "v2/webrtc/tokens", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcTokensListResponse, + parse_obj_as( + type_=WebRtcTokensListResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def create( + self, + *, + sip_trunk: str, + payload: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + ttl: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[WebRtcTokenResponse]: + """ + Creates a Wavix Embeddable widget token that authenticates a browser-based softphone session. The token expires after `ttl` seconds. + + Parameters + ---------- + sip_trunk : str + Name of the SIP trunk the token authenticates against. + + payload : typing.Optional[typing.Dict[str, typing.Any]] + Arbitrary client-defined data to associate with the token. + + ttl : typing.Optional[int] + Time to live in seconds. Pass `null` for no expiration. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WebRtcTokenResponse] + Returns the created widget token. + """ + _response = self._client_wrapper.httpx_client.request( + "v2/webrtc/tokens", + method="POST", + json={ + "sip_trunk": sip_trunk, + "payload": payload, + "ttl": ttl, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcTokenResponse, + parse_obj_as( + type_=WebRtcTokenResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[WebRtcToken]: + """ + Returns the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WebRtcToken] + Returns the widget token. + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcToken, + parse_obj_as( + type_=WebRtcToken, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def update( + self, id: str, *, payload: typing.Dict[str, typing.Any], request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[WebRtcToken]: + """ + Updates the `payload` carried by the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to update. + + payload : typing.Dict[str, typing.Any] + Arbitrary client-defined data to associate with the token, replacing the existing payload. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[WebRtcToken] + Returns the updated widget token. + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="PUT", + json={ + "payload": payload, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcToken, + parse_obj_as( + type_=WebRtcToken, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[SuccessResponse]: + """ + Deletes the Wavix Embeddable widget token identified by `id`. The token can no longer authenticate widget sessions, and any active session using it ends. + + Parameters + ---------- + id : str + The UUID of the widget token to delete. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SuccessResponse] + Returns a success confirmation. The widget token is deleted. + """ + _response = self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawTokensClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list( + self, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WebRtcTokensListResponse]: + """ + Returns a paginated list of active Wavix Embeddable widget tokens for the authenticated account. + + Parameters + ---------- + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WebRtcTokensListResponse] + Returns a paginated list of active widget tokens. + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/webrtc/tokens", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcTokensListResponse, + parse_obj_as( + type_=WebRtcTokensListResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create( + self, + *, + sip_trunk: str, + payload: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, + ttl: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[WebRtcTokenResponse]: + """ + Creates a Wavix Embeddable widget token that authenticates a browser-based softphone session. The token expires after `ttl` seconds. + + Parameters + ---------- + sip_trunk : str + Name of the SIP trunk the token authenticates against. + + payload : typing.Optional[typing.Dict[str, typing.Any]] + Arbitrary client-defined data to associate with the token. + + ttl : typing.Optional[int] + Time to live in seconds. Pass `null` for no expiration. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WebRtcTokenResponse] + Returns the created widget token. + """ + _response = await self._client_wrapper.httpx_client.request( + "v2/webrtc/tokens", + method="POST", + json={ + "sip_trunk": sip_trunk, + "payload": payload, + "ttl": ttl, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcTokenResponse, + parse_obj_as( + type_=WebRtcTokenResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def get( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WebRtcToken]: + """ + Returns the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to retrieve. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WebRtcToken] + Returns the widget token. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcToken, + parse_obj_as( + type_=WebRtcToken, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def update( + self, id: str, *, payload: typing.Dict[str, typing.Any], request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[WebRtcToken]: + """ + Updates the `payload` carried by the Wavix Embeddable widget token identified by `id`. + + Parameters + ---------- + id : str + The UUID of the widget token to update. + + payload : typing.Dict[str, typing.Any] + Arbitrary client-defined data to associate with the token, replacing the existing payload. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[WebRtcToken] + Returns the updated widget token. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="PUT", + json={ + "payload": payload, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + WebRtcToken, + parse_obj_as( + type_=WebRtcToken, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete( + self, id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[SuccessResponse]: + """ + Deletes the Wavix Embeddable widget token identified by `id`. The token can no longer authenticate widget sessions, and any active session using it ends. + + Parameters + ---------- + id : str + The UUID of the widget token to delete. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SuccessResponse] + Returns a success confirmation. The widget token is deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + f"v2/webrtc/tokens/{encode_path_param(id)}", + method="DELETE", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SuccessResponse, + parse_obj_as( + type_=SuccessResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + UnauthorizedErrorResponse, + parse_obj_as( + type_=UnauthorizedErrorResponse, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..25710db --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,21 @@ +import pytest + + +def _has_httpx_aiohttp() -> bool: + """Check if httpx_aiohttp is importable.""" + try: + import httpx_aiohttp # type: ignore[import-not-found] # noqa: F401 + + return True + except ImportError: + return False + + +def pytest_collection_modifyitems(config: pytest.Config, items: list) -> None: + """Auto-skip @pytest.mark.aiohttp tests when httpx_aiohttp is not installed.""" + if _has_httpx_aiohttp(): + return + skip_aiohttp = pytest.mark.skip(reason="httpx_aiohttp not installed") + for item in items: + if "aiohttp" in item.keywords: + item.add_marker(skip_aiohttp) diff --git a/tests/custom/test_client.py b/tests/custom/test_client.py new file mode 100644 index 0000000..ab04ce6 --- /dev/null +++ b/tests/custom/test_client.py @@ -0,0 +1,7 @@ +import pytest + + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True diff --git a/tests/test_aiohttp_autodetect.py b/tests/test_aiohttp_autodetect.py new file mode 100644 index 0000000..ce34a25 --- /dev/null +++ b/tests/test_aiohttp_autodetect.py @@ -0,0 +1,116 @@ +import importlib +import sys +import unittest +from unittest import mock + +import httpx +import pytest + + +class TestMakeDefaultAsyncClientWithoutAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp is NOT installed.""" + + def test_returns_httpx_async_client(self) -> None: + """When httpx_aiohttp is not installed, returns plain httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from wavix.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When follow_redirects is None, omits it from httpx.AsyncClient.""" + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + from wavix.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx.AsyncClient) + self.assertFalse(client.follow_redirects) + + def test_explicit_httpx_client_bypasses_autodetect(self) -> None: + """When user passes httpx_client explicitly, _make_default_async_client is not called.""" + + explicit_client = httpx.AsyncClient(timeout=120) + with mock.patch("wavix.client._make_default_async_client") as mock_make: + # Replicate the generated conditional: httpx_client if httpx_client is not None else _make_default_async_client(...) + result = explicit_client if explicit_client is not None else mock_make(timeout=60, follow_redirects=True) + mock_make.assert_not_called() + self.assertIs(result, explicit_client) + + +@pytest.mark.aiohttp +class TestMakeDefaultAsyncClientWithAiohttp(unittest.TestCase): + """Tests for _make_default_async_client when httpx_aiohttp IS installed.""" + + def test_returns_aiohttp_client(self) -> None: + """When httpx_aiohttp is installed, returns HttpxAiohttpClient.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from wavix.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=True) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, 60) + self.assertTrue(client.follow_redirects) + + def test_follow_redirects_none(self) -> None: + """When httpx_aiohttp is installed and follow_redirects is None, omits it.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from wavix.client import _make_default_async_client + + client = _make_default_async_client(timeout=60, follow_redirects=None) + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertFalse(client.follow_redirects) + + +class TestDefaultClientsWithoutAiohttp(unittest.TestCase): + """Tests for _default_clients.py convenience classes (no aiohttp).""" + + def test_default_async_httpx_client_defaults(self) -> None: + """DefaultAsyncHttpxClient applies SDK defaults.""" + from wavix._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient() + self.assertIsInstance(client, httpx.AsyncClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) + + def test_default_async_httpx_client_overrides(self) -> None: + """DefaultAsyncHttpxClient allows overriding defaults.""" + from wavix._default_clients import DefaultAsyncHttpxClient + + client = DefaultAsyncHttpxClient(timeout=30, follow_redirects=False) + self.assertEqual(client.timeout.read, 30) + self.assertFalse(client.follow_redirects) + + def test_default_aiohttp_client_raises_without_package(self) -> None: + """DefaultAioHttpClient raises RuntimeError when httpx_aiohttp not installed.""" + import wavix._default_clients + + with mock.patch.dict(sys.modules, {"httpx_aiohttp": None}): + importlib.reload(wavix._default_clients) + + with self.assertRaises(RuntimeError) as ctx: + wavix._default_clients.DefaultAioHttpClient() + self.assertIn("pip install wavix-python-sdk[aiohttp]", str(ctx.exception)) + + importlib.reload(wavix._default_clients) + + +@pytest.mark.aiohttp +class TestDefaultClientsWithAiohttp(unittest.TestCase): + """Tests for _default_clients.py when httpx_aiohttp IS installed.""" + + def test_default_aiohttp_client_defaults(self) -> None: + """DefaultAioHttpClient works when httpx_aiohttp is installed.""" + import httpx_aiohttp # type: ignore[import-not-found] + + from wavix._default_clients import SDK_DEFAULT_TIMEOUT, DefaultAioHttpClient + + client = DefaultAioHttpClient() + self.assertIsInstance(client, httpx_aiohttp.HttpxAiohttpClient) + self.assertEqual(client.timeout.read, SDK_DEFAULT_TIMEOUT) + self.assertTrue(client.follow_redirects) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..f3ea265 --- /dev/null +++ b/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/tests/utils/assets/models/__init__.py b/tests/utils/assets/models/__init__.py new file mode 100644 index 0000000..2cf0126 --- /dev/null +++ b/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/tests/utils/assets/models/circle.py b/tests/utils/assets/models/circle.py new file mode 100644 index 0000000..94b04a2 --- /dev/null +++ b/tests/utils/assets/models/circle.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from wavix.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/tests/utils/assets/models/color.py b/tests/utils/assets/models/color.py new file mode 100644 index 0000000..2aa2c4c --- /dev/null +++ b/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/tests/utils/assets/models/object_with_defaults.py b/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 0000000..a977b1d --- /dev/null +++ b/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/tests/utils/assets/models/object_with_optional_field.py b/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 0000000..ec78df3 --- /dev/null +++ b/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +from wavix.core.serialization import FieldMetadata + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Optional[typing.Any] diff --git a/tests/utils/assets/models/shape.py b/tests/utils/assets/models/shape.py new file mode 100644 index 0000000..734b39d --- /dev/null +++ b/tests/utils/assets/models/shape.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions + +from wavix.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/tests/utils/assets/models/square.py b/tests/utils/assets/models/square.py new file mode 100644 index 0000000..48d8952 --- /dev/null +++ b/tests/utils/assets/models/square.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + +from wavix.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/tests/utils/assets/models/undiscriminated_shape.py b/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 0000000..99f12b3 --- /dev/null +++ b/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/tests/utils/test_http_client.py b/tests/utils/test_http_client.py new file mode 100644 index 0000000..c004626 --- /dev/null +++ b/tests/utils/test_http_client.py @@ -0,0 +1,694 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict +from unittest.mock import AsyncMock, MagicMock, patch + +import httpx +import pytest + +from wavix.core.http_client import ( + AsyncHttpClient, + HttpClient, + _build_url, + _should_retry, + get_request_body, + remove_none_from_dict, +) +from wavix.core.request_options import RequestOptions + + +# Stub clients for testing HttpClient and AsyncHttpClient +class _DummySyncClient: + """A minimal stub for httpx.Client that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyAsyncClient: + """A minimal stub for httpx.AsyncClient that records request arguments.""" + + def __init__(self) -> None: + self.last_request_kwargs: Dict[str, Any] = {} + + async def request(self, **kwargs: Any) -> "_DummyResponse": + self.last_request_kwargs = kwargs + return _DummyResponse() + + +class _DummyResponse: + """A minimal stub for httpx.Response.""" + + status_code = 200 + headers: Dict[str, str] = {} + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def get_request_options_with_none() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later", "optional": None}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + """Test that implicit empty bodies (json=None) are collapsed to None.""" + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + +def test_explicit_empty_json_body_is_preserved() -> None: + """Test that explicit empty bodies (json={}) are preserved and sent as {}. + + This is important for endpoints where the request body is required but all + fields are optional. The server expects valid JSON ({}) not an empty body. + """ + unrelated_request_options: RequestOptions = {"max_retries": 3} + + # Explicit json={} should be preserved + json_body, data_body = get_request_body(json={}, data=None, request_options=unrelated_request_options, omit=None) + assert json_body == {} + assert data_body is None + + # Explicit data={} should also be preserved + json_body2, data_body2 = get_request_body(json=None, data={}, request_options=unrelated_request_options, omit=None) + assert json_body2 is None + assert data_body2 == {} + + +def test_json_body_preserves_none_values() -> None: + """Test that JSON bodies preserve None values (they become JSON null).""" + json_body, data_body = get_request_body( + json={"hello": "world", "optional": None}, data=None, request_options=None, omit=None + ) + # JSON bodies should preserve None values + assert json_body == {"hello": "world", "optional": None} + assert data_body is None + + +def test_data_body_preserves_none_values_without_multipart() -> None: + """Test that data bodies preserve None values when not using multipart. + + The filtering of None values happens in HttpClient.request/stream methods, + not in get_request_body. This test verifies get_request_body doesn't filter None. + """ + json_body, data_body = get_request_body( + json=None, data={"hello": "world", "optional": None}, request_options=None, omit=None + ) + # get_request_body should preserve None values in data body + # The filtering happens later in HttpClient.request when multipart is detected + assert data_body == {"hello": "world", "optional": None} + assert json_body is None + + +def test_remove_none_from_dict_filters_none_values() -> None: + """Test that remove_none_from_dict correctly filters out None values.""" + original = {"hello": "world", "optional": None, "another": "value", "also_none": None} + filtered = remove_none_from_dict(original) + assert filtered == {"hello": "world", "another": "value"} + # Original should not be modified + assert original == {"hello": "world", "optional": None, "another": "value", "also_none": None} + + +def test_remove_none_from_dict_empty_dict() -> None: + """Test that remove_none_from_dict handles empty dict.""" + assert remove_none_from_dict({}) == {} + + +def test_remove_none_from_dict_all_none() -> None: + """Test that remove_none_from_dict handles dict with all None values.""" + assert remove_none_from_dict({"a": None, "b": None}) == {} + + +def test_http_client_does_not_pass_empty_params_list() -> None: + """Test that HttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + # Use a path with query params (e.g., pagination cursor URL) + http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +def test_http_client_passes_encoded_params_when_present() -> None: + """Test that HttpClient passes encoded params when params are provided.""" + dummy_client = _DummySyncClient() + http_client = HttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + ) + + http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +@pytest.mark.asyncio +async def test_async_http_client_does_not_pass_empty_params_list() -> None: + """Test that AsyncHttpClient passes params=None when params are empty. + + This prevents httpx from stripping existing query parameters from the URL, + which happens when params=[] or params={} is passed. + """ + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + # Use a path with query params (e.g., pagination cursor URL) + await http_client.request( + path="resource?after=123", + method="GET", + params=None, + request_options=None, + ) + + # We care that httpx receives params=None, not [] or {} + assert "params" in dummy_client.last_request_kwargs + assert dummy_client.last_request_kwargs["params"] is None + + # Verify the query string in the URL is preserved + url = str(dummy_client.last_request_kwargs["url"]) + assert "after=123" in url, f"Expected query param 'after=123' in URL, got: {url}" + + +@pytest.mark.asyncio +async def test_async_http_client_passes_encoded_params_when_present() -> None: + """Test that AsyncHttpClient passes encoded params when params are provided.""" + dummy_client = _DummyAsyncClient() + http_client = AsyncHttpClient( + httpx_client=dummy_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com/resource", + async_base_headers=None, + ) + + await http_client.request( + path="", + method="GET", + params={"after": "456"}, + request_options=None, + ) + + params = dummy_client.last_request_kwargs["params"] + # For a simple dict, encode_query should give a single (key, value) tuple + assert params == [("after", "456")] + + +def test_basic_url_joining() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com", "/users") + assert result == "https://api.example.com/users" + + +def test_basic_url_joining_trailing_slash() -> None: + """Test basic URL joining with a simple base URL and path.""" + result = _build_url("https://api.example.com/", "/users") + assert result == "https://api.example.com/users" + + +def test_preserves_base_url_path_prefix() -> None: + """Test that path prefixes in base URL are preserved. + + This is the critical bug fix - urllib.parse.urljoin() would strip + the path prefix when the path starts with '/'. + """ + result = _build_url("https://cloud.example.com/org/tenant/api", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +def test_preserves_base_url_path_prefix_trailing_slash() -> None: + """Test that path prefixes in base URL are preserved.""" + result = _build_url("https://cloud.example.com/org/tenant/api/", "/users") + assert result == "https://cloud.example.com/org/tenant/api/users" + + +# --------------------------------------------------------------------------- +# Connection error retry tests +# --------------------------------------------------------------------------- + + +def _make_sync_http_client(mock_client: Any) -> HttpClient: + return HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + ) + + +def _make_async_http_client(mock_client: Any) -> AsyncHttpClient: + return AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + async_base_headers=None, + ) + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_connect_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_retries_on_remote_protocol_error(mock_sleep: MagicMock) -> None: + """Sync: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + http_client = _make_sync_http_client(mock_client) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_exhausts_retries(mock_sleep: MagicMock) -> None: + """Sync: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_connection_error_respects_max_retries_zero(mock_sleep: MagicMock) -> None: + """Sync: connection error respects max_retries=0.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = _make_sync_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 0}, + ) + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_connect_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.ConnectError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_retries_on_remote_protocol_error(mock_sleep: AsyncMock) -> None: + """Async: connection error retries on httpx.RemoteProtocolError.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.RemoteProtocolError("Remote end closed connection without response"), + _DummyResponse(), + ] + ) + http_client = _make_async_http_client(mock_client) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + assert mock_client.request.call_count == 2 + mock_sleep.assert_called_once() + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_connection_error_exhausts_retries(mock_sleep: AsyncMock) -> None: + """Async: connection error exhausts retries then raises.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = _make_async_http_client(mock_client) + + with pytest.raises(httpx.ConnectError): + await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + assert mock_sleep.call_count == 2 + + +# --------------------------------------------------------------------------- +# base_max_retries constructor parameter tests +# --------------------------------------------------------------------------- + + +def test_sync_http_client_default_base_max_retries() -> None: + """HttpClient defaults to base_max_retries=2.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_async_http_client_default_base_max_retries() -> None: + """AsyncHttpClient defaults to base_max_retries=2.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + ) + assert http_client.base_max_retries == 2 + + +def test_sync_http_client_custom_base_max_retries() -> None: + """HttpClient accepts a custom base_max_retries value.""" + http_client = HttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +def test_async_http_client_custom_base_max_retries() -> None: + """AsyncHttpClient accepts a custom base_max_retries value.""" + http_client = AsyncHttpClient( + httpx_client=MagicMock(), # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_max_retries=5, + ) + assert http_client.base_max_retries == 5 + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_zero_disables_retries(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request.side_effect = httpx.ConnectError("connection failed") + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_zero_disables_retries(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries=0 disables retries when no request_options override.""" + mock_client = MagicMock() + mock_client.request = AsyncMock(side_effect=httpx.ConnectError("connection failed")) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, + ) + + with pytest.raises(httpx.ConnectError): + await http_client.request(path="/test", method="GET") + + # No retries, just the initial attempt + assert mock_client.request.call_count == 1 + mock_sleep.assert_not_called() + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_request_options_override_base_max_retries(mock_sleep: MagicMock) -> None: + """Sync: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_request_options_override_base_max_retries(mock_sleep: AsyncMock) -> None: + """Async: request_options max_retries overrides base_max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("connection failed"), + httpx.ConnectError("connection failed"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=0, # base says no retries + ) + + # But request_options overrides to allow 2 retries + response = await http_client.request( + path="/test", + method="GET", + request_options={"max_retries": 2}, + ) + + assert response.status_code == 200 + # 1 initial + 2 retries = 3 total attempts + assert mock_client.request.call_count == 3 + + +@patch("wavix.core.http_client.time.sleep", return_value=None) +def test_sync_base_max_retries_used_as_default(mock_sleep: MagicMock) -> None: + """Sync: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request.side_effect = [ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + http_client = HttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 + + +@pytest.mark.asyncio +@patch("wavix.core.http_client.asyncio.sleep", new_callable=AsyncMock) +async def test_async_base_max_retries_used_as_default(mock_sleep: AsyncMock) -> None: + """Async: base_max_retries is used when request_options has no max_retries.""" + mock_client = MagicMock() + mock_client.request = AsyncMock( + side_effect=[ + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + httpx.ConnectError("fail"), + _DummyResponse(), + ] + ) + http_client = AsyncHttpClient( + httpx_client=mock_client, # type: ignore[arg-type] + base_timeout=lambda: None, + base_headers=lambda: {}, + base_url=lambda: "https://example.com", + base_max_retries=3, + ) + + response = await http_client.request(path="/test", method="GET") + + assert response.status_code == 200 + # 1 initial + 3 retries = 4 total attempts + assert mock_client.request.call_count == 4 + + +# --------------------------------------------------------------------------- +# _should_retry unit tests +# --------------------------------------------------------------------------- + + +def _make_response(status_code: int) -> httpx.Response: + return httpx.Response(status_code=status_code, content=b"") + + +@pytest.mark.parametrize( + "status_code", + [408, 409, 429, 500, 501, 502, 503, 504, 599], +) +def test_should_retry_retryable_status_codes(status_code: int) -> None: + """Legacy mode: retries on 408, 409, 429, and all >= 500.""" + assert _should_retry(_make_response(status_code)) is True + + +@pytest.mark.parametrize( + "status_code", + [200, 201, 301, 400, 401, 403, 404], +) +def test_should_not_retry_non_retryable_status_codes(status_code: int) -> None: + assert _should_retry(_make_response(status_code)) is False + + +def test_should_retry_599_upper_boundary() -> None: + """Legacy mode retries on >= 500, which includes 599.""" + assert _should_retry(_make_response(599)) is True diff --git a/tests/utils/test_query_encoding.py b/tests/utils/test_query_encoding.py new file mode 100644 index 0000000..c29f898 --- /dev/null +++ b/tests/utils/test_query_encoding.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +from wavix.core.query_encoder import encode_query + + +def test_query_encoding_deep_objects() -> None: + assert encode_query({"hello world": "hello world"}) == [("hello world", "hello world")] + assert encode_query({"hello_world": {"hello": "world"}}) == [("hello_world[hello]", "world")] + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == [ + ("hello_world[hello][world]", "today"), + ("hello_world[test]", "this"), + ("hi", "there"), + ] + + +def test_query_encoding_deep_object_arrays() -> None: + assert encode_query({"objects": [{"key": "hello", "value": "world"}, {"key": "foo", "value": "bar"}]}) == [ + ("objects[key]", "hello"), + ("objects[value]", "world"), + ("objects[key]", "foo"), + ("objects[value]", "bar"), + ] + assert encode_query( + {"users": [{"name": "string", "tags": ["string"]}, {"name": "string2", "tags": ["string2", "string3"]}]} + ) == [ + ("users[name]", "string"), + ("users[tags]", "string"), + ("users[name]", "string2"), + ("users[tags]", "string2"), + ("users[tags]", "string3"), + ] + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded is None diff --git a/tests/utils/test_serialization.py b/tests/utils/test_serialization.py new file mode 100644 index 0000000..767b3a9 --- /dev/null +++ b/tests/utils/test_serialization.py @@ -0,0 +1,72 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +from wavix.core.serialization import convert_and_respect_annotation_metadata + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" + ) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") + assert converted == data