diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caa50bcd..a5e74733 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,19 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/knock-mapi-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | @@ -30,12 +35,57 @@ jobs: - name: Run lints run: ./scripts/lint + build: + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/knock-mapi-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + steps: + - uses: actions/checkout@v6 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + + - name: Get GitHub OIDC Token + if: |- + github.repository == 'stainless-sdks/knock-mapi-python' && + !startsWith(github.ref, 'refs/heads/stl/') + id: github-oidc + uses: actions/github-script@v8 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: |- + github.repository == 'stainless-sdks/knock-mapi-python' && + !startsWith(github.ref, 'refs/heads/stl/') + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/knock-mapi-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..41cf9ad4 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,31 @@ +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to PyPI in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/knocklabs/knock-mgmt-python/actions/workflows/publish-pypi.yml +name: Publish PyPI +on: + workflow_dispatch: + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Publish to PyPI + run: | + bash ./bin/publish-pypi + env: + PYPI_TOKEN: ${{ secrets.KNOCK_MGMT_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 00000000..9d7864d8 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,21 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'knocklabs/knock-mgmt-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v6 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + PYPI_TOKEN: ${{ secrets.KNOCK_MGMT_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 87797408..95ceb189 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .prism.log -.vscode _dev __pycache__ diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..ba6c3483 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0-alpha.1" +} \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 02525abd..220d1bc7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 35 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-mapi-305e73d67d2964dc9148b0f091d040e1d2899960af5815ecac64089110085804.yml -openapi_spec_hash: 90b9e75af20d6209e1a294d7227120fc -config_hash: bb4cfc20c3c1e1a40c6a9f4c5ea209df +configured_endpoints: 64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-mapi-d350e605aaabe0e1ac88c0632aa838b55beeb43757e54e4474abf3f1607e76b1.yml +openapi_spec_hash: f70289a7e9fccb400f4fbcdf44158201 +config_hash: 6faa327b884de9362f7b76260eae1b71 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5b010307 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.importFormat": "relative", +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..b0632b63 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,161 @@ +# Changelog + +## 0.1.0-alpha.1 (2026-03-12) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/knocklabs/knock-mgmt-python/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) + +### Features + +* **api:** add missing in-app feed step ([f2d16ba](https://github.com/knocklabs/knock-mgmt-python/commit/f2d16ba352ccce262afc098c2a9e11107fa6335d)) +* **api:** add new guide methods ([6b9a363](https://github.com/knocklabs/knock-mgmt-python/commit/6b9a3630f2860dd489a73ef9b3a205eeba54038b)) +* **api:** add support for broadcasts ([dfff3b6](https://github.com/knocklabs/knock-mgmt-python/commit/dfff3b6aace731ce3cbb498d84760801806d0b0c)) +* **api:** add variable get ([f8014da](https://github.com/knocklabs/knock-mgmt-python/commit/f8014da201bbe7e115a7f3763d22c5f6dc9fe7a9)) +* **api:** added audience methods and models ([bd881e4](https://github.com/knocklabs/knock-mgmt-python/commit/bd881e4a59f6ae794a3976e7c3c79d40fece0833)) +* **api:** added channel config ([1947ae9](https://github.com/knocklabs/knock-mgmt-python/commit/1947ae9546876e5bdcf9986bc98e5f8c44b2f75d)) +* **api:** api update ([7057f71](https://github.com/knocklabs/knock-mgmt-python/commit/7057f711076bd6a871fbab0de338ccadbe44b9f5)) +* **api:** api update ([48bd43b](https://github.com/knocklabs/knock-mgmt-python/commit/48bd43bcddf574cc1f204fcaa9902538d77c3158)) +* **api:** api update ([826716d](https://github.com/knocklabs/knock-mgmt-python/commit/826716d70dc0eb48b63e3aea1942662bf09744f7)) +* **api:** api update ([ecb0afa](https://github.com/knocklabs/knock-mgmt-python/commit/ecb0afabae18fa4ed9a1e20caf0e0b17cd65b468)) +* **api:** api update ([49ad1fe](https://github.com/knocklabs/knock-mgmt-python/commit/49ad1fe3552fdb259694e5ebe2b972dfce631062)) +* **api:** api update ([573b32b](https://github.com/knocklabs/knock-mgmt-python/commit/573b32bb2c885619b8a750c4c821ce9eb609ca02)) +* **api:** api update ([5d1a897](https://github.com/knocklabs/knock-mgmt-python/commit/5d1a8970d822dfc54011a4b827b7220daf41c0f6)) +* **api:** api update ([a3f47c8](https://github.com/knocklabs/knock-mgmt-python/commit/a3f47c828828f3212011805310c5aa227ee64613)) +* **api:** api update ([3b214e2](https://github.com/knocklabs/knock-mgmt-python/commit/3b214e2fb3ef77bf69b5ff2aa81181b4a4f7fdbc)) +* **api:** api update ([103bb9d](https://github.com/knocklabs/knock-mgmt-python/commit/103bb9d498f0dc40a0cd4681cf5bc175af12bf02)) +* **api:** api update ([ecacf07](https://github.com/knocklabs/knock-mgmt-python/commit/ecacf0764e1d45cffafe55c365558d708e0519cf)) +* **api:** api update ([334f9fd](https://github.com/knocklabs/knock-mgmt-python/commit/334f9fd617897e29402f982a20701f79a01bee15)) +* **api:** api update ([930b015](https://github.com/knocklabs/knock-mgmt-python/commit/930b0154c135afa17600ab3c219b4b1f19343aef)) +* **api:** api update ([6d39044](https://github.com/knocklabs/knock-mgmt-python/commit/6d390443ccbd5d253934b818c56da66ec97ff0c1)) +* **api:** api update ([efd462e](https://github.com/knocklabs/knock-mgmt-python/commit/efd462e78cd69882e252c6b28ff736ba42858cde)) +* **api:** api update ([1147326](https://github.com/knocklabs/knock-mgmt-python/commit/11473265366a20a8678f2a6a801550914f1a96b0)) +* **api:** api update ([5d935b5](https://github.com/knocklabs/knock-mgmt-python/commit/5d935b50d3752a7c885549760491d6eada87af0f)) +* **api:** api update ([1a42f16](https://github.com/knocklabs/knock-mgmt-python/commit/1a42f1693e913406628e03abbe60b09d7cb3d41e)) +* **api:** api update ([8551cb2](https://github.com/knocklabs/knock-mgmt-python/commit/8551cb25785952ce171db00938ac497851b79808)) +* **api:** api update ([b8b1166](https://github.com/knocklabs/knock-mgmt-python/commit/b8b11665898ad6c9b8d14a718f9b0aa5e75f72ac)) +* **api:** api update ([ecd1f65](https://github.com/knocklabs/knock-mgmt-python/commit/ecd1f6597124d6fe8c29fedc5870bc3783798f1c)) +* **api:** api update ([0d8db91](https://github.com/knocklabs/knock-mgmt-python/commit/0d8db9179968f0f2852473054d207ca5e8de3652)) +* **api:** api update ([6b36373](https://github.com/knocklabs/knock-mgmt-python/commit/6b36373b2cad5c9cccf12a10f8c257b6e97bceae)) +* **api:** api update ([cdf34d7](https://github.com/knocklabs/knock-mgmt-python/commit/cdf34d7231f4907b906279d4f80b57d695acc3d2)) +* **api:** api update ([4563ca2](https://github.com/knocklabs/knock-mgmt-python/commit/4563ca203fcb58abedf61de44bc07c4721f28c94)) +* **api:** api update ([3b657ad](https://github.com/knocklabs/knock-mgmt-python/commit/3b657ada4768db559b1ee96fc69b1f7f34c736c1)) +* **api:** api update ([1ec851f](https://github.com/knocklabs/knock-mgmt-python/commit/1ec851f416e69862f0eb5e98d3eec7bda2dcb4c5)) +* **api:** api update ([83ec5df](https://github.com/knocklabs/knock-mgmt-python/commit/83ec5df95b6865f85e0d3ff24eb061e567c87727)) +* **api:** api update ([6e33c35](https://github.com/knocklabs/knock-mgmt-python/commit/6e33c35c2b5e0e6cd24295edbde61796e6a2f1a3)) +* **api:** api update ([ab44013](https://github.com/knocklabs/knock-mgmt-python/commit/ab44013d8a3f67eee6f5c15e0d580844c7b0a965)) +* **api:** api update ([72a6e7f](https://github.com/knocklabs/knock-mgmt-python/commit/72a6e7f4c68c3cf1a755f3a6953cb79c3c63ac53)) +* **api:** api update ([4416f12](https://github.com/knocklabs/knock-mgmt-python/commit/4416f123258f4b2ab0bebc6c2f17297b9bb04bcc)) +* **api:** api update ([e44de16](https://github.com/knocklabs/knock-mgmt-python/commit/e44de16209fbd7c8fadcbb6478ef216b887bc8d7)) +* **api:** api update ([2e53b16](https://github.com/knocklabs/knock-mgmt-python/commit/2e53b16a3636da45f5378470b334f25b37e9bf27)) +* **api:** api update ([c08046d](https://github.com/knocklabs/knock-mgmt-python/commit/c08046dd8dca83f69eb0b3c4ab6eef57a88ac963)) +* **api:** api update ([36dcd95](https://github.com/knocklabs/knock-mgmt-python/commit/36dcd956b0f8f4881546271d21bccd8b774863aa)) +* **api:** api update ([7008cf7](https://github.com/knocklabs/knock-mgmt-python/commit/7008cf73bfd891d6f4021077c5d63d00b93e7669)) +* **api:** api update ([7399b9f](https://github.com/knocklabs/knock-mgmt-python/commit/7399b9fc0d9c3c46e903cf1183d120397d2af9d3)) +* **api:** api update ([0c1b410](https://github.com/knocklabs/knock-mgmt-python/commit/0c1b41076bed59f3996d9141afc9e73fca1769b2)) +* **api:** api update ([7575d7b](https://github.com/knocklabs/knock-mgmt-python/commit/7575d7b140452cfd8d5c88a3e5e5d4a28b9b6b9f)) +* **api:** api update ([bc2a719](https://github.com/knocklabs/knock-mgmt-python/commit/bc2a719d1aa7fad4194e872ca1c3240c1c042f3f)) +* **api:** api update ([0cad2fb](https://github.com/knocklabs/knock-mgmt-python/commit/0cad2fb280ed744324ded81614829bc683e68a14)) +* **api:** api update ([3cc26f1](https://github.com/knocklabs/knock-mgmt-python/commit/3cc26f16ba95884a10f9fa8669ca9851d0894847)) +* **api:** api update ([21c2b12](https://github.com/knocklabs/knock-mgmt-python/commit/21c2b12a5d87b3987f8fff6859993f2054c44b3f)) +* **api:** api update ([07b0bf9](https://github.com/knocklabs/knock-mgmt-python/commit/07b0bf9ef7044d022823c67bbee43de5ecde93e8)) +* **api:** api update ([3d7feb9](https://github.com/knocklabs/knock-mgmt-python/commit/3d7feb907fb15da8046849fd2bbfbb4f714ee0a2)) +* **api:** api update ([2377550](https://github.com/knocklabs/knock-mgmt-python/commit/23775502141f16fd94989dd3124107e2db4bf0e9)) +* **api:** api update ([7c582f2](https://github.com/knocklabs/knock-mgmt-python/commit/7c582f2a91271f759be8f01e15d33f0652012ff7)) +* **api:** api update ([a65462c](https://github.com/knocklabs/knock-mgmt-python/commit/a65462cbf700ce4955f56b2e670eaa1f2219d78e)) +* **api:** api update ([0bfd014](https://github.com/knocklabs/knock-mgmt-python/commit/0bfd0143c63ab168dcb28f5b49e251f05cfbc6f3)) +* **api:** api update ([b0cf9fc](https://github.com/knocklabs/knock-mgmt-python/commit/b0cf9fcb27c993980e3da9aa6a886efae70c8c38)) +* **api:** api update ([a2de1b8](https://github.com/knocklabs/knock-mgmt-python/commit/a2de1b8da15d469c9050b7d814dc2276a7dde6cf)) +* **api:** api update ([7af3170](https://github.com/knocklabs/knock-mgmt-python/commit/7af3170696535565aa1d576d1dbc754b0e64ff81)) +* **api:** api update ([11d9193](https://github.com/knocklabs/knock-mgmt-python/commit/11d9193173be598000253571040bd523128ea457)) +* **api:** latest spec and config ([b06ccba](https://github.com/knocklabs/knock-mgmt-python/commit/b06ccba6f9b3791a6cbbbd19ad9b27fbceed0067)) +* **api:** manual updates ([f30d56e](https://github.com/knocklabs/knock-mgmt-python/commit/f30d56e2100adf166eb3ad19e0781057b264aac4)) +* **api:** manual updates ([05e23bb](https://github.com/knocklabs/knock-mgmt-python/commit/05e23bbcdb37a1a203a66cb2cef6ddd5e0b4a18d)) +* **api:** manual updates ([b6872b1](https://github.com/knocklabs/knock-mgmt-python/commit/b6872b1258b3f80b1e312965c131bea193a98390)) +* **api:** manual updates ([f34e0da](https://github.com/knocklabs/knock-mgmt-python/commit/f34e0da48e9e27518f13eed6d8612578d72b7a42)) +* **api:** manual updates ([070beab](https://github.com/knocklabs/knock-mgmt-python/commit/070beab81747bc7eac7426c124b29b710cb21b07)) +* **api:** manual updates ([29415bd](https://github.com/knocklabs/knock-mgmt-python/commit/29415bd6f87d68167f11ce4aaa71cff02fa55460)) +* **api:** manual updates ([6deba0f](https://github.com/knocklabs/knock-mgmt-python/commit/6deba0f2e192260d524c7ac454cbcbed8458b745)) +* **client:** add custom JSON encoder for extended type support ([d0b7c95](https://github.com/knocklabs/knock-mgmt-python/commit/d0b7c9552e938a8e11996bfa03380285c46dbdea)) +* **client:** add follow_redirects request option ([5079fb9](https://github.com/knocklabs/knock-mgmt-python/commit/5079fb92ff59e10bc72b2af1489724f56fcd3911)) +* **client:** add support for aiohttp ([0a040e2](https://github.com/knocklabs/knock-mgmt-python/commit/0a040e281f0077e1707d4225654510a42a3a2fb0)) +* **client:** add support for binary request streaming ([d189e5b](https://github.com/knocklabs/knock-mgmt-python/commit/d189e5bf626b427f025011d85e24efe0f2cfb0be)) +* improve future compat with pydantic v3 ([e278ead](https://github.com/knocklabs/knock-mgmt-python/commit/e278eada17a2cdbcb3115f9574a2863fa7690223)) +* **types:** replace List[str] with SequenceNotStr in params ([b683c79](https://github.com/knocklabs/knock-mgmt-python/commit/b683c790f8eb0a6acd4141e455b04433023d8cec)) + + +### Bug Fixes + +* avoid newer type syntax ([8e12c75](https://github.com/knocklabs/knock-mgmt-python/commit/8e12c750a414ffbb92fac0cb1509de522031a0e0)) +* **ci:** correct conditional ([a96a2bb](https://github.com/knocklabs/knock-mgmt-python/commit/a96a2bbd60bd1b9d6b93dd5f83ba658f4934eeae)) +* **ci:** release-doctor — report correct token name ([7021893](https://github.com/knocklabs/knock-mgmt-python/commit/7021893dab2b10080eb01e6a5e665e421ce30e6f)) +* **client:** close streams without requiring full consumption ([b3228b1](https://github.com/knocklabs/knock-mgmt-python/commit/b3228b101a92a0dbba0f330b7992db943525b37c)) +* **client:** correctly parse binary response | stream ([8d0c46b](https://github.com/knocklabs/knock-mgmt-python/commit/8d0c46bca0475d69f0100ced4db27560b0f18b54)) +* compat with Python 3.14 ([50bf20a](https://github.com/knocklabs/knock-mgmt-python/commit/50bf20a54a3ed5b684f2160016bdc0432c1436a6)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([7d0d80e](https://github.com/knocklabs/knock-mgmt-python/commit/7d0d80e4bfd30d6f569003b346ab60b218f6a9e5)) +* ensure streams are always closed ([ee545af](https://github.com/knocklabs/knock-mgmt-python/commit/ee545af593640a9128ffcc8e14b0793e91ec8a5f)) +* **package:** support direct resource imports ([1cb0a5c](https://github.com/knocklabs/knock-mgmt-python/commit/1cb0a5c285e1965ae02d91c89d1e58b5cf4bf7c9)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([f3148fd](https://github.com/knocklabs/knock-mgmt-python/commit/f3148fd2c048f4572c24e7e970c12c8f8e9be96e)) +* use async_to_httpx_files in patch method ([2cc7a7f](https://github.com/knocklabs/knock-mgmt-python/commit/2cc7a7f8c482a7cb6f3e567ea0ddc843d38fdcc3)) + + +### Chores + +* add missing docstrings ([1d22dd0](https://github.com/knocklabs/knock-mgmt-python/commit/1d22dd0866f56e690df4a5bd3197b1a71f0fd19d)) +* bump `httpx-aiohttp` version to 0.1.9 ([c6c6d98](https://github.com/knocklabs/knock-mgmt-python/commit/c6c6d98ec14a7429e16a41aa361b5ec94cbca623)) +* change publish docs url ([aac5b46](https://github.com/knocklabs/knock-mgmt-python/commit/aac5b4663dfda3909e1133acad7fbbf843cdc726)) +* **ci:** change upload type ([06bf7fe](https://github.com/knocklabs/knock-mgmt-python/commit/06bf7febdcdfec47d98cc82a9419e90c375846eb)) +* **ci:** enable for pull requests ([43b4165](https://github.com/knocklabs/knock-mgmt-python/commit/43b4165055db298bf6625629b1eb38d64ab821ff)) +* **ci:** fix installation instructions ([136b83e](https://github.com/knocklabs/knock-mgmt-python/commit/136b83e5dcad2f71b08f48b536dd0e9f463f5a8e)) +* **ci:** only run for pushes and fork pull requests ([f04705e](https://github.com/knocklabs/knock-mgmt-python/commit/f04705e5d5f746990f96f2f950972a8733ff672d)) +* **ci:** skip uploading artifacts on stainless-internal branches ([80598cb](https://github.com/knocklabs/knock-mgmt-python/commit/80598cba32fb840e5dbc2da0aba9518175de01c4)) +* **ci:** upgrade `actions/github-script` ([8d0b70e](https://github.com/knocklabs/knock-mgmt-python/commit/8d0b70e32a6457cf02b4b289d0208f4570122499)) +* **ci:** upload sdks to package manager ([526aeec](https://github.com/knocklabs/knock-mgmt-python/commit/526aeecc6c6975207ad58e88eed5adc15c04e27d)) +* configure new SDK language ([acf21f3](https://github.com/knocklabs/knock-mgmt-python/commit/acf21f3de35f214f603433f8defee406c0f9a769)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([828d403](https://github.com/knocklabs/knock-mgmt-python/commit/828d403035cb6fcb789b92069124eba8c79088a6)) +* do not install brew dependencies in ./scripts/bootstrap by default ([47f30ba](https://github.com/knocklabs/knock-mgmt-python/commit/47f30baf8a98c54137ee207d0e91c9dd0e271cc4)) +* **docs:** add missing descriptions ([4ea0e56](https://github.com/knocklabs/knock-mgmt-python/commit/4ea0e56000f9a8bb71e87c50d778cebae7a8df95)) +* **docs:** grammar improvements ([b623da9](https://github.com/knocklabs/knock-mgmt-python/commit/b623da9bf447b1df85c9cc200996aa24685de3de)) +* **docs:** remove reference to rye shell ([2ce93ae](https://github.com/knocklabs/knock-mgmt-python/commit/2ce93ae3634eeaf460ddccd643bc4b20d02f5cdc)) +* **docs:** use environment variables for authentication in code snippets ([36bffb1](https://github.com/knocklabs/knock-mgmt-python/commit/36bffb16af013addda71caa6974d6ccb88c50f70)) +* format all `api.md` files ([e5783c6](https://github.com/knocklabs/knock-mgmt-python/commit/e5783c60d584fc5c4c48b0ddc56e25513ebd4255)) +* **internal/tests:** avoid race condition with implicit client cleanup ([e2038a8](https://github.com/knocklabs/knock-mgmt-python/commit/e2038a8ccc329b3661f667d48f9f3b0422dd902a)) +* **internal:** add `--fix` argument to lint script ([9363b78](https://github.com/knocklabs/knock-mgmt-python/commit/9363b78ea3c049f1712ea9c5c8d37371913313c6)) +* **internal:** add missing files argument to base client ([ea01174](https://github.com/knocklabs/knock-mgmt-python/commit/ea01174beaa38a05e72661d451743db515ba8de0)) +* **internal:** add request options to SSE classes ([6ea8aa7](https://github.com/knocklabs/knock-mgmt-python/commit/6ea8aa78f3ec0a14548ca8520aff3c9cb319176a)) +* **internal:** add Sequence related utils ([5c33cb2](https://github.com/knocklabs/knock-mgmt-python/commit/5c33cb28c71fb192aed4579e1aec0eb7e2e09a71)) +* **internal:** avoid errors for isinstance checks on proxies ([7947d31](https://github.com/knocklabs/knock-mgmt-python/commit/7947d31302f3e0e3d43ba21e84aaf0a06e541f5b)) +* **internal:** bump dependencies ([5df07ae](https://github.com/knocklabs/knock-mgmt-python/commit/5df07ae3c334b0fb1a7c4bc34dde90853e24a400)) +* **internal:** change ci workflow machines ([914029b](https://github.com/knocklabs/knock-mgmt-python/commit/914029ba51826437a11129275e5df48d049a4c57)) +* **internal:** codegen related update ([59321bf](https://github.com/knocklabs/knock-mgmt-python/commit/59321bf400e00ca67ef0ca5c07d02e2169aa3d02)) +* **internal:** codegen related update ([6e2e025](https://github.com/knocklabs/knock-mgmt-python/commit/6e2e025ae38d38d50423ff08c4c0171eac8ba5bb)) +* **internal:** codegen related update ([8ffd5af](https://github.com/knocklabs/knock-mgmt-python/commit/8ffd5af66c23e840f4f7be4892b092c862fad587)) +* **internal:** detect missing future annotations with ruff ([39d08bb](https://github.com/knocklabs/knock-mgmt-python/commit/39d08bb5ee700407596d84b9b73e52b38c5ac76b)) +* **internal:** fix lint error on Python 3.14 ([1cdccd2](https://github.com/knocklabs/knock-mgmt-python/commit/1cdccd22890ea7d793242ec6f1b305077877007f)) +* **internal:** grammar fix (it's -> its) ([ee64945](https://github.com/knocklabs/knock-mgmt-python/commit/ee64945b8a4aa9fe9a6fc275e9128a9467c691fa)) +* **internal:** improve examples ([0680566](https://github.com/knocklabs/knock-mgmt-python/commit/068056627b37ad3595d1f35949525c17849351fb)) +* **internal:** make `test_proxy_environment_variables` more resilient ([b8c5605](https://github.com/knocklabs/knock-mgmt-python/commit/b8c5605a153e3c489d005f6184c595d4a1d59bee)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([ac8c299](https://github.com/knocklabs/knock-mgmt-python/commit/ac8c29957a25f0efb14cd6de0ec4bc841e7782ca)) +* **internal:** move mypy configurations to `pyproject.toml` file ([e1c9f43](https://github.com/knocklabs/knock-mgmt-python/commit/e1c9f432350da84660420a1bfc2ac49a813ccd11)) +* **internal:** remove mock server code ([83b9cfa](https://github.com/knocklabs/knock-mgmt-python/commit/83b9cfa542ca79c25abcdd7fc5b2f6849ce9b1e0)) +* **internal:** update `actions/checkout` version ([e2d5634](https://github.com/knocklabs/knock-mgmt-python/commit/e2d563420f920f38a2560ded9ae42f1aca92940c)) +* **internal:** update conftest.py ([2cbbdbc](https://github.com/knocklabs/knock-mgmt-python/commit/2cbbdbcebfd41c2d85f00a6bf88b5fdf9f710c9d)) +* **internal:** update pydantic dependency ([7defb9e](https://github.com/knocklabs/knock-mgmt-python/commit/7defb9ea2c11bbe100c1427f1f89e50038cbec30)) +* **internal:** update pyright exclude list ([6545a42](https://github.com/knocklabs/knock-mgmt-python/commit/6545a423e252a107e9aab73aee0b8ce3df7e083e)) +* **package:** drop Python 3.8 support ([8e266a2](https://github.com/knocklabs/knock-mgmt-python/commit/8e266a26c0677603f2aa2e98b1bd59b29ccd0b46)) +* **readme:** update badges ([bb17cec](https://github.com/knocklabs/knock-mgmt-python/commit/bb17cec1d07a1b218882dcaff2b8019122efa156)) +* speedup initial import ([589d59b](https://github.com/knocklabs/knock-mgmt-python/commit/589d59b073e5c0293f8c910875d3c6d68ccd9b54)) +* **tests:** add tests for httpx client instantiation & proxies ([69f66be](https://github.com/knocklabs/knock-mgmt-python/commit/69f66beff3e47d5247968cecb2de84a0d7ec658d)) +* **tests:** run tests in parallel ([c4adff6](https://github.com/knocklabs/knock-mgmt-python/commit/c4adff61602002577b5240580dc941028e3eb17f)) +* **tests:** simplify `get_platform` test ([dacad65](https://github.com/knocklabs/knock-mgmt-python/commit/dacad65d1ef5042803226b3f2f584a7ce16aa197)) +* **tests:** skip some failing tests on the latest python versions ([0d15d2b](https://github.com/knocklabs/knock-mgmt-python/commit/0d15d2b669b62c4927bd71c31e92cad6a5be78fd)) +* **test:** update skip reason message ([5b57627](https://github.com/knocklabs/knock-mgmt-python/commit/5b57627184a682c5f7090d5f787453b35308b583)) +* **types:** change optional parameter type from NotGiven to Omit ([02af8f0](https://github.com/knocklabs/knock-mgmt-python/commit/02af8f0a2b0904f9c1b031a14d0b2424f1c286f9)) +* **types:** rebuild Pydantic models after all types are defined ([17498d4](https://github.com/knocklabs/knock-mgmt-python/commit/17498d45291c5d7518d490947d00ccd1fd4cc055)) +* update github action ([ba727ca](https://github.com/knocklabs/knock-mgmt-python/commit/ba727ca6dd9372015c3924ab11faa3e94214f98d)) +* update lockfile ([b80f066](https://github.com/knocklabs/knock-mgmt-python/commit/b80f066cb4de4d97dc2171086d6b48dec1341029)) +* update mock server docs ([d9cfa90](https://github.com/knocklabs/knock-mgmt-python/commit/d9cfa9086e25454ba0ef08679635affdc31d37a7)) +* update SDK settings ([0e4d241](https://github.com/knocklabs/knock-mgmt-python/commit/0e4d24129716e977791255bd26561a97ed3191dc)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([f85ddbe](https://github.com/knocklabs/knock-mgmt-python/commit/f85ddbee1539127553bd259d61db9a155de2268e)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc3ee0f1..417cf230 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix @@ -63,7 +62,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ pip install git+ssh://git@github.com/stainless-sdks/knock-mapi-python.git +$ pip install git+ssh://git@github.com/knocklabs/knock-mgmt-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -86,13 +85,6 @@ $ pip install ./path-to-wheel-file.whl ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - -```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml -``` - ```sh $ ./scripts/test ``` @@ -121,7 +113,7 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/knock-mapi-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/knocklabs/knock-mgmt-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually diff --git a/LICENSE b/LICENSE index 473d35f4..d0d8d68f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Knock Mgmt + Copyright 2026 Knock Mgmt Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 0a09cc2a..a679cdef 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Knock Mgmt Python API library -[![PyPI version](https://img.shields.io/pypi/v/knock_mapi.svg)](https://pypi.org/project/knock_mapi/) + +[![PyPI version](https://img.shields.io/pypi/v/knock_mapi.svg?label=pypi%20(stable))](https://pypi.org/project/knock_mapi/) -The Knock Mgmt Python library provides convenient access to the Knock Mgmt REST API from any Python 3.8+ +The Knock Mgmt Python library provides convenient access to the Knock Mgmt REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -15,12 +16,12 @@ The REST API documentation can be found on [docs.knock.app](https://docs.knock.a ## Installation ```sh -# install from this staging repo -pip install git+ssh://git@github.com/stainless-sdks/knock-mapi-python.git +# install from the production repo +pip install git+ssh://git@github.com/knocklabs/knock-mgmt-python.git ``` > [!NOTE] -> Once this package is [published to PyPI](https://app.stainless.com/docs/guides/publish), this will become: `pip install --pre knock_mapi` +> Once this package is [published to PyPI](https://www.stainless.com/docs/guides/publish), this will become: `pip install --pre knock_mapi` ## Usage @@ -71,6 +72,42 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from the production repo +pip install 'knock_mapi[aiohttp] @ git+ssh://git@github.com/knocklabs/knock-mgmt-python.git' +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from knock_mapi import DefaultAioHttpClient +from knock_mapi import AsyncKnockMgmt + + +async def main() -> None: + async with AsyncKnockMgmt( + service_token=os.environ.get( + "KNOCK_SERVICE_TOKEN" + ), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + page = await client.workflows.list( + environment="development", + ) + print(page.entries) + + +asyncio.run(main()) +``` + ## Using types Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: @@ -247,7 +284,7 @@ client.with_options(max_retries=5).workflows.list( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from knock_mapi import KnockMgmt @@ -316,9 +353,9 @@ workflow = response.parse() # get the object that `workflows.list()` would have print(workflow.valid) ``` -These methods return an [`APIResponse`](https://github.com/stainless-sdks/knock-mapi-python/tree/main/src/knock_mapi/_response.py) object. +These methods return an [`APIResponse`](https://github.com/knocklabs/knock-mgmt-python/tree/main/src/knock_mapi/_response.py) object. -The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/knock-mapi-python/tree/main/src/knock_mapi/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. +The async client returns an [`AsyncAPIResponse`](https://github.com/knocklabs/knock-mgmt-python/tree/main/src/knock_mapi/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. #### `.with_streaming_response` @@ -424,7 +461,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/knock-mapi-python/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/knocklabs/knock-mgmt-python/issues) with questions, bugs, or suggestions. ### Determining the installed version @@ -439,7 +476,7 @@ print(knock_mapi.__version__) ## Requirements -Python 3.8 or higher. +Python 3.9 or higher. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 5936da4c..b4557181 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Knock Mgmt please follow the respective company's security reporting guidelines. +or products provided by Knock Mgmt, please follow the respective company's security reporting guidelines. ### Knock Mgmt Terms and Policies -Please contact support@knock.app for any questions or concerns regarding security of our services. +Please contact support@knock.app for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index 8151c128..95b50fb5 100644 --- a/api.md +++ b/api.md @@ -1,7 +1,18 @@ # Shared Types ```python -from knock_mapi.types import PageInfo +from knock_mapi.types import ( + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, + MessageTypeTextareaField, + MessageTypeURLField, + PageInfo, +) ``` # Templates @@ -102,14 +113,26 @@ from knock_mapi.types import ( Duration, SendWindow, Workflow, + WorkflowAIAgentStep, WorkflowBatchStep, WorkflowBranchStep, - WorkflowChannelStep, + WorkflowChatStep, WorkflowDelayStep, + WorkflowEmailStep, WorkflowFetchStep, + WorkflowInAppFeedStep, + WorkflowPushStep, + WorkflowRandomCohortStep, + WorkflowSMSStep, WorkflowStep, WorkflowThrottleStep, WorkflowTriggerWorkflowStep, + WorkflowUpdateDataStep, + WorkflowUpdateObjectStep, + WorkflowUpdateTenantStep, + WorkflowUpdateUserStep, + WorkflowWebhookStep, + WorkflowRetrieveResponse, WorkflowActivateResponse, WorkflowRunResponse, WorkflowUpsertResponse, @@ -119,7 +142,7 @@ from knock_mapi.types import ( Methods: -- client.workflows.retrieve(workflow_key, \*\*params) -> Workflow +- client.workflows.retrieve(workflow_key, \*\*params) -> WorkflowRetrieveResponse - client.workflows.list(\*\*params) -> SyncEntriesCursor[Workflow] - client.workflows.activate(workflow_key, \*\*params) -> WorkflowActivateResponse - client.workflows.run(workflow_key, \*\*params) -> WorkflowRunResponse @@ -188,12 +211,15 @@ Methods: Types: ```python -from knock_mapi.types import ChannelGroup, ChannelGroupRule +from knock_mapi.types import ChannelGroup, ChannelGroupRule, ChannelGroupUpsertResponse ``` Methods: +- client.channel_groups.retrieve(channel_group_key) -> ChannelGroup - client.channel_groups.list(\*\*params) -> SyncEntriesCursor[ChannelGroup] +- client.channel_groups.delete(channel_group_key) -> None +- client.channel_groups.upsert(channel_group_key, \*\*params) -> ChannelGroupUpsertResponse # Channels @@ -202,6 +228,7 @@ Types: ```python from knock_mapi.types import ( Channel, + ChannelEnvironmentSettings, ChatChannelSettings, EmailChannelSettings, InAppFeedChannelSettings, @@ -212,8 +239,23 @@ from knock_mapi.types import ( Methods: +- client.channels.retrieve(channel_key) -> Channel - client.channels.list(\*\*params) -> SyncEntriesCursor[Channel] +# Members + +Types: + +```python +from knock_mapi.types import Member, MemberUser +``` + +Methods: + +- client.members.retrieve(id) -> Member +- client.members.list(\*\*params) -> SyncEntriesCursor[Member] +- client.members.delete(id) -> None + # Environments Types: @@ -237,4 +279,93 @@ from knock_mapi.types import Variable Methods: +- client.variables.retrieve(key) -> Variable - client.variables.list(\*\*params) -> SyncEntriesCursor[Variable] + +# Guides + +Types: + +```python +from knock_mapi.types import ( + Guide, + GuideActivationURLPattern, + GuideStep, + GuideActivateResponse, + GuideArchiveResponse, + GuideUpsertResponse, + GuideValidateResponse, +) +``` + +Methods: + +- client.guides.retrieve(guide_key, \*\*params) -> Guide +- client.guides.list(\*\*params) -> SyncEntriesCursor[Guide] +- client.guides.activate(guide_key, \*\*params) -> GuideActivateResponse +- client.guides.archive(guide_key) -> GuideArchiveResponse +- client.guides.upsert(guide_key, \*\*params) -> GuideUpsertResponse +- client.guides.validate(guide_key, \*\*params) -> GuideValidateResponse + +# Branches + +Types: + +```python +from knock_mapi.types import Branch +``` + +Methods: + +- client.branches.create(branch_slug, \*\*params) -> Branch +- client.branches.retrieve(branch_slug, \*\*params) -> Branch +- client.branches.list(\*\*params) -> SyncEntriesCursor[Branch] +- client.branches.delete(branch_slug, \*\*params) -> None + +# Broadcasts + +Types: + +```python +from knock_mapi.types import ( + Broadcast, + BroadcastRequest, + BroadcastCancelResponse, + BroadcastSendResponse, + BroadcastUpsertResponse, + BroadcastValidateResponse, +) +``` + +Methods: + +- client.broadcasts.retrieve(broadcast_key, \*\*params) -> Broadcast +- client.broadcasts.list(\*\*params) -> SyncEntriesCursor[Broadcast] +- client.broadcasts.cancel(broadcast_key, \*\*params) -> BroadcastCancelResponse +- client.broadcasts.send(broadcast_key, \*\*params) -> BroadcastSendResponse +- client.broadcasts.upsert(broadcast_key, \*\*params) -> BroadcastUpsertResponse +- client.broadcasts.validate(broadcast_key, \*\*params) -> BroadcastValidateResponse + +# Audiences + +Types: + +```python +from knock_mapi.types import ( + Audience, + AudienceCondition, + DynamicAudience, + StaticAudience, + AudienceArchiveResponse, + AudienceUpsertResponse, + AudienceValidateResponse, +) +``` + +Methods: + +- client.audiences.retrieve(audience_key, \*\*params) -> Audience +- client.audiences.list(\*\*params) -> SyncEntriesCursor[Audience] +- client.audiences.archive(audience_key, \*\*params) -> AudienceArchiveResponse +- client.audiences.upsert(audience_key, \*\*params) -> AudienceUpsertResponse +- client.audiences.validate(audience_key, \*\*params) -> AudienceValidateResponse diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 00000000..b845b0f4 --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${PYPI_TOKEN}" ]; then + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index be72489c..00000000 --- a/mypy.ini +++ /dev/null @@ -1,50 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ^(src/knock_mapi/_files\.py|_dev/.*\.py|tests/.*)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value,overload-cannot-match - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index a3caaee7..5584a060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,32 @@ [project] name = "knock_mapi" -version = "0.0.1-alpha.0" +version = "0.1.0-alpha.1" description = "The official Python library for the knock mgmt API" dynamic = ["readme"] license = "Apache-2.0" authors = [ { name = "Knock Mgmt", email = "support@knock.app" }, ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.10, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] -requires-python = ">= 3.8" + +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "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", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -34,16 +37,18 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/stainless-sdks/knock-mapi-python" -Repository = "https://github.com/stainless-sdks/knock-mapi-python" +Homepage = "https://github.com/knocklabs/knock-mgmt-python" +Repository = "https://github.com/knocklabs/knock-mgmt-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] [tool.rye] managed = true # version pins are in requirements-dev.lock dev-dependencies = [ "pyright==1.1.399", - "mypy", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", @@ -53,7 +58,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -64,7 +69,7 @@ format = { chain = [ # run formatting again to fix any inconsistencies when imports are stripped "format:ruff", ]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" "lint" = { chain = [ @@ -121,11 +126,11 @@ path = "README.md" [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] # replace relative links with absolute links pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' -replacement = '[\1](https://github.com/stainless-sdks/knock-mapi-python/tree/main/\g<2>)' +replacement = '[\1](https://github.com/knocklabs/knock-mgmt-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" @@ -138,12 +143,13 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.8" +pythonVersion = "3.9" exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true @@ -152,10 +158,62 @@ reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ['src/knock_mapi/_files.py', '_dev/.*.py', 'tests/.*'] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + + [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" [tool.ruff.format] docstring-code-format = true @@ -168,6 +226,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -190,6 +250,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..a77036b4 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,66 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "python", + "extra-files": [ + "src/knock_mapi/_version.py" + ] +} \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index 2840953e..98b492b9 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,95 +10,140 @@ # universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via knock-mapi +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via knock-mapi -argcomplete==3.1.2 +argcomplete==3.6.3 + # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp # via nox -certifi==2023.7.22 +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2026.1.4 # via httpcore # via httpx -colorlog==6.7.0 +colorlog==6.10.1 + # via nox +dependency-groups==1.3.1 # via nox -dirty-equals==0.6.0 -distlib==0.3.7 +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv -distro==1.8.0 +distro==1.9.0 # via knock-mapi -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio # via pytest -filelock==3.12.4 +execnet==2.1.2 + # via pytest-xdist +filelock==3.19.1 # via virtualenv -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 + # via httpx-aiohttp # via knock-mapi # via respx -idna==3.4 +httpx-aiohttp==0.1.12 + # via knock-mapi +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx -importlib-metadata==7.0.0 -iniconfig==2.0.0 + # via yarl +importlib-metadata==8.7.1 +iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.14.1 -mypy-extensions==1.0.0 +multidict==6.7.0 + # via aiohttp + # via yarl +mypy==1.17.0 +mypy-extensions==1.1.0 # via mypy -nest-asyncio==1.6.0 -nodeenv==1.8.0 +nodeenv==1.10.0 # via pyright -nox==2023.4.22 -packaging==23.2 +nox==2025.11.12 +packaging==25.0 + # via dependency-groups # via nox # via pytest -platformdirs==3.11.0 +pathspec==1.0.3 + # via mypy +platformdirs==4.4.0 # via virtualenv -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -pydantic==2.10.3 +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via knock-mapi -pydantic-core==2.27.1 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich pyright==1.1.399 -pytest==8.3.3 +pytest==8.4.2 # via pytest-asyncio -pytest-asyncio==0.24.0 -python-dateutil==2.8.2 + # via pytest-xdist +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 # via time-machine -pytz==2023.3.post1 - # via dirty-equals respx==0.22.0 -rich==13.7.1 -ruff==0.9.4 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +rich==14.2.0 +ruff==0.14.13 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via knock-mapi -time-machine==2.9.0 -tomli==2.0.2 +time-machine==2.19.0 +tomli==2.4.0 + # via dependency-groups # via mypy + # via nox # via pytest -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup # via knock-mapi + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright -virtualenv==20.24.5 + # via pytest-asyncio + # via typing-inspection + # via virtualenv +typing-inspection==0.4.2 + # via pydantic +virtualenv==20.36.1 # via nox -zipp==3.17.0 +yarl==1.22.0 + # via aiohttp +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 40285372..834f20c4 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,36 +10,67 @@ # universal: false -e file:. -annotated-types==0.6.0 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via httpx-aiohttp + # via knock-mapi +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via knock-mapi -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp +certifi==2026.1.4 # via httpcore # via httpx -distro==1.8.0 +distro==1.9.0 # via knock-mapi -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio -h11==0.14.0 +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 + # via httpx-aiohttp + # via knock-mapi +httpx-aiohttp==0.1.12 # via knock-mapi -idna==3.4 +idna==3.11 # via anyio # via httpx -pydantic==2.10.3 + # via yarl +multidict==6.7.0 + # via aiohttp + # via yarl +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via knock-mapi -pydantic-core==2.27.1 +pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via knock-mapi -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup # via knock-mapi + # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.2 + # via pydantic +yarl==1.22.0 + # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap index e84fe62c..b430fee3 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/scripts/lint b/scripts/lint index 6c845f52..ef5ce141 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import knock_mapi' diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index d2814ae6..00000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" -fi diff --git a/scripts/test b/scripts/test index 2b878456..39729d09 100755 --- a/scripts/test +++ b/scripts/test @@ -4,53 +4,7 @@ set -e cd "$(dirname "$0")/.." -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi export DEFER_PYDANTIC_BUILD=false diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..22e98293 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -exuo pipefail + +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/knock-mapi-python/$SHA/$FILENAME'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/knock_mapi/__init__.py b/src/knock_mapi/__init__.py index ea3e26cd..e41a2d20 100644 --- a/src/knock_mapi/__init__.py +++ b/src/knock_mapi/__init__.py @@ -1,7 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import ( Client, @@ -34,7 +36,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -46,7 +48,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "KnockMgmtError", "APIError", "APIStatusError", @@ -76,8 +80,12 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/knock_mapi/_base_client.py b/src/knock_mapi/_base_client.py index 426b03d3..97b768df 100644 --- a/src/knock_mapi/_base_client.py +++ b/src/knock_mapi/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -42,7 +43,6 @@ from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -52,14 +52,17 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import PYDANTIC_V2, model_copy, model_dump +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -83,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -145,9 +149,9 @@ def __init__( def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - json: Body | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url self.json = json @@ -232,7 +236,7 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model @@ -320,7 +324,7 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model @@ -477,8 +481,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -529,6 +544,26 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): + kwargs["content"] = json_data + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +575,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data if is_given(json_data) else None, - files=files, **kwargs, ) @@ -585,7 +618,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -815,7 +848,7 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -960,6 +993,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1068,7 +1104,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1174,6 +1217,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1186,6 +1230,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1199,6 +1244,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1211,13 +1257,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1227,9 +1285,24 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1238,11 +1311,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1252,9 +1337,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1279,6 +1374,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1287,8 +1400,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): @@ -1314,7 +1431,7 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1460,6 +1577,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1568,7 +1688,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") @@ -1662,6 +1789,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1674,6 +1802,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1687,6 +1816,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1699,13 +1829,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1715,9 +1857,29 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, + ) return await self.request(cast_to, opts) async def put( @@ -1726,11 +1888,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1740,9 +1914,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( @@ -1766,8 +1950,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/knock_mapi/_client.py b/src/knock_mapi/_client.py index fb65def1..b20b06bd 100644 --- a/src/knock_mapi/_client.py +++ b/src/knock_mapi/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping +from typing import TYPE_CHECKING, Any, Mapping from typing_extensions import Self, override import httpx @@ -11,29 +11,17 @@ from . import _exceptions from ._qs import Querystring from ._types import ( - NOT_GIVEN, Omit, Timeout, NotGiven, Transport, ProxiesTypes, RequestOptions, + not_given, ) from ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ -from .resources import ( - auth, - commits, - api_keys, - channels, - partials, - variables, - environments, - translations, - email_layouts, - message_types, - channel_groups, -) from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, KnockMgmtError from ._base_client import ( @@ -41,7 +29,44 @@ SyncAPIClient, AsyncAPIClient, ) -from .resources.workflows import workflows + +if TYPE_CHECKING: + from .resources import ( + auth, + guides, + commits, + members, + api_keys, + branches, + channels, + partials, + audiences, + variables, + workflows, + broadcasts, + environments, + translations, + email_layouts, + message_types, + channel_groups, + ) + from .resources.auth import AuthResource, AsyncAuthResource + from .resources.guides import GuidesResource, AsyncGuidesResource + from .resources.commits import CommitsResource, AsyncCommitsResource + from .resources.members import MembersResource, AsyncMembersResource + from .resources.api_keys import APIKeysResource, AsyncAPIKeysResource + from .resources.branches import BranchesResource, AsyncBranchesResource + from .resources.channels import ChannelsResource, AsyncChannelsResource + from .resources.partials import PartialsResource, AsyncPartialsResource + from .resources.audiences import AudiencesResource, AsyncAudiencesResource + from .resources.variables import VariablesResource, AsyncVariablesResource + from .resources.broadcasts import BroadcastsResource, AsyncBroadcastsResource + from .resources.environments import EnvironmentsResource, AsyncEnvironmentsResource + from .resources.translations import TranslationsResource, AsyncTranslationsResource + from .resources.email_layouts import EmailLayoutsResource, AsyncEmailLayoutsResource + from .resources.message_types import MessageTypesResource, AsyncMessageTypesResource + from .resources.channel_groups import ChannelGroupsResource, AsyncChannelGroupsResource + from .resources.workflows.workflows import WorkflowsResource, AsyncWorkflowsResource __all__ = [ "Timeout", @@ -56,21 +81,6 @@ class KnockMgmt(SyncAPIClient): - email_layouts: email_layouts.EmailLayoutsResource - commits: commits.CommitsResource - partials: partials.PartialsResource - translations: translations.TranslationsResource - workflows: workflows.WorkflowsResource - message_types: message_types.MessageTypesResource - auth: auth.AuthResource - api_keys: api_keys.APIKeysResource - channel_groups: channel_groups.ChannelGroupsResource - channels: channels.ChannelsResource - environments: environments.EnvironmentsResource - variables: variables.VariablesResource - with_raw_response: KnockMgmtWithRawResponse - with_streaming_response: KnockMgmtWithStreamedResponse - # client options service_token: str @@ -79,7 +89,7 @@ def __init__( *, service_token: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -125,20 +135,132 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.email_layouts = email_layouts.EmailLayoutsResource(self) - self.commits = commits.CommitsResource(self) - self.partials = partials.PartialsResource(self) - self.translations = translations.TranslationsResource(self) - self.workflows = workflows.WorkflowsResource(self) - self.message_types = message_types.MessageTypesResource(self) - self.auth = auth.AuthResource(self) - self.api_keys = api_keys.APIKeysResource(self) - self.channel_groups = channel_groups.ChannelGroupsResource(self) - self.channels = channels.ChannelsResource(self) - self.environments = environments.EnvironmentsResource(self) - self.variables = variables.VariablesResource(self) - self.with_raw_response = KnockMgmtWithRawResponse(self) - self.with_streaming_response = KnockMgmtWithStreamedResponse(self) + @cached_property + def email_layouts(self) -> EmailLayoutsResource: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import EmailLayoutsResource + + return EmailLayoutsResource(self) + + @cached_property + def commits(self) -> CommitsResource: + """Commits are versioned changes to resources.""" + from .resources.commits import CommitsResource + + return CommitsResource(self) + + @cached_property + def partials(self) -> PartialsResource: + """Partials allow you to reuse content across templates.""" + from .resources.partials import PartialsResource + + return PartialsResource(self) + + @cached_property + def translations(self) -> TranslationsResource: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import TranslationsResource + + return TranslationsResource(self) + + @cached_property + def workflows(self) -> WorkflowsResource: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import WorkflowsResource + + return WorkflowsResource(self) + + @cached_property + def message_types(self) -> MessageTypesResource: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import MessageTypesResource + + return MessageTypesResource(self) + + @cached_property + def auth(self) -> AuthResource: + """Resources for managing your Knock account.""" + from .resources.auth import AuthResource + + return AuthResource(self) + + @cached_property + def api_keys(self) -> APIKeysResource: + from .resources.api_keys import APIKeysResource + + return APIKeysResource(self) + + @cached_property + def channel_groups(self) -> ChannelGroupsResource: + from .resources.channel_groups import ChannelGroupsResource + + return ChannelGroupsResource(self) + + @cached_property + def channels(self) -> ChannelsResource: + from .resources.channels import ChannelsResource + + return ChannelsResource(self) + + @cached_property + def members(self) -> MembersResource: + from .resources.members import MembersResource + + return MembersResource(self) + + @cached_property + def environments(self) -> EnvironmentsResource: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import EnvironmentsResource + + return EnvironmentsResource(self) + + @cached_property + def variables(self) -> VariablesResource: + from .resources.variables import VariablesResource + + return VariablesResource(self) + + @cached_property + def guides(self) -> GuidesResource: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import GuidesResource + + return GuidesResource(self) + + @cached_property + def branches(self) -> BranchesResource: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import BranchesResource + + return BranchesResource(self) + + @cached_property + def broadcasts(self) -> BroadcastsResource: + from .resources.broadcasts import BroadcastsResource + + return BroadcastsResource(self) + + @cached_property + def audiences(self) -> AudiencesResource: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AudiencesResource + + return AudiencesResource(self) + + @cached_property + def with_raw_response(self) -> KnockMgmtWithRawResponse: + return KnockMgmtWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> KnockMgmtWithStreamedResponse: + return KnockMgmtWithStreamedResponse(self) @property @override @@ -165,9 +287,9 @@ def copy( *, service_token: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -246,21 +368,6 @@ def _make_status_error( class AsyncKnockMgmt(AsyncAPIClient): - email_layouts: email_layouts.AsyncEmailLayoutsResource - commits: commits.AsyncCommitsResource - partials: partials.AsyncPartialsResource - translations: translations.AsyncTranslationsResource - workflows: workflows.AsyncWorkflowsResource - message_types: message_types.AsyncMessageTypesResource - auth: auth.AsyncAuthResource - api_keys: api_keys.AsyncAPIKeysResource - channel_groups: channel_groups.AsyncChannelGroupsResource - channels: channels.AsyncChannelsResource - environments: environments.AsyncEnvironmentsResource - variables: variables.AsyncVariablesResource - with_raw_response: AsyncKnockMgmtWithRawResponse - with_streaming_response: AsyncKnockMgmtWithStreamedResponse - # client options service_token: str @@ -269,7 +376,7 @@ def __init__( *, service_token: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -315,20 +422,132 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.email_layouts = email_layouts.AsyncEmailLayoutsResource(self) - self.commits = commits.AsyncCommitsResource(self) - self.partials = partials.AsyncPartialsResource(self) - self.translations = translations.AsyncTranslationsResource(self) - self.workflows = workflows.AsyncWorkflowsResource(self) - self.message_types = message_types.AsyncMessageTypesResource(self) - self.auth = auth.AsyncAuthResource(self) - self.api_keys = api_keys.AsyncAPIKeysResource(self) - self.channel_groups = channel_groups.AsyncChannelGroupsResource(self) - self.channels = channels.AsyncChannelsResource(self) - self.environments = environments.AsyncEnvironmentsResource(self) - self.variables = variables.AsyncVariablesResource(self) - self.with_raw_response = AsyncKnockMgmtWithRawResponse(self) - self.with_streaming_response = AsyncKnockMgmtWithStreamedResponse(self) + @cached_property + def email_layouts(self) -> AsyncEmailLayoutsResource: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import AsyncEmailLayoutsResource + + return AsyncEmailLayoutsResource(self) + + @cached_property + def commits(self) -> AsyncCommitsResource: + """Commits are versioned changes to resources.""" + from .resources.commits import AsyncCommitsResource + + return AsyncCommitsResource(self) + + @cached_property + def partials(self) -> AsyncPartialsResource: + """Partials allow you to reuse content across templates.""" + from .resources.partials import AsyncPartialsResource + + return AsyncPartialsResource(self) + + @cached_property + def translations(self) -> AsyncTranslationsResource: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import AsyncTranslationsResource + + return AsyncTranslationsResource(self) + + @cached_property + def workflows(self) -> AsyncWorkflowsResource: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import AsyncWorkflowsResource + + return AsyncWorkflowsResource(self) + + @cached_property + def message_types(self) -> AsyncMessageTypesResource: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import AsyncMessageTypesResource + + return AsyncMessageTypesResource(self) + + @cached_property + def auth(self) -> AsyncAuthResource: + """Resources for managing your Knock account.""" + from .resources.auth import AsyncAuthResource + + return AsyncAuthResource(self) + + @cached_property + def api_keys(self) -> AsyncAPIKeysResource: + from .resources.api_keys import AsyncAPIKeysResource + + return AsyncAPIKeysResource(self) + + @cached_property + def channel_groups(self) -> AsyncChannelGroupsResource: + from .resources.channel_groups import AsyncChannelGroupsResource + + return AsyncChannelGroupsResource(self) + + @cached_property + def channels(self) -> AsyncChannelsResource: + from .resources.channels import AsyncChannelsResource + + return AsyncChannelsResource(self) + + @cached_property + def members(self) -> AsyncMembersResource: + from .resources.members import AsyncMembersResource + + return AsyncMembersResource(self) + + @cached_property + def environments(self) -> AsyncEnvironmentsResource: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import AsyncEnvironmentsResource + + return AsyncEnvironmentsResource(self) + + @cached_property + def variables(self) -> AsyncVariablesResource: + from .resources.variables import AsyncVariablesResource + + return AsyncVariablesResource(self) + + @cached_property + def guides(self) -> AsyncGuidesResource: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import AsyncGuidesResource + + return AsyncGuidesResource(self) + + @cached_property + def branches(self) -> AsyncBranchesResource: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import AsyncBranchesResource + + return AsyncBranchesResource(self) + + @cached_property + def broadcasts(self) -> AsyncBroadcastsResource: + from .resources.broadcasts import AsyncBroadcastsResource + + return AsyncBroadcastsResource(self) + + @cached_property + def audiences(self) -> AsyncAudiencesResource: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AsyncAudiencesResource + + return AsyncAudiencesResource(self) + + @cached_property + def with_raw_response(self) -> AsyncKnockMgmtWithRawResponse: + return AsyncKnockMgmtWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncKnockMgmtWithStreamedResponse: + return AsyncKnockMgmtWithStreamedResponse(self) @property @override @@ -355,9 +574,9 @@ def copy( *, service_token: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -436,67 +655,507 @@ def _make_status_error( class KnockMgmtWithRawResponse: + _client: KnockMgmt + def __init__(self, client: KnockMgmt) -> None: - self.email_layouts = email_layouts.EmailLayoutsResourceWithRawResponse(client.email_layouts) - self.commits = commits.CommitsResourceWithRawResponse(client.commits) - self.partials = partials.PartialsResourceWithRawResponse(client.partials) - self.translations = translations.TranslationsResourceWithRawResponse(client.translations) - self.workflows = workflows.WorkflowsResourceWithRawResponse(client.workflows) - self.message_types = message_types.MessageTypesResourceWithRawResponse(client.message_types) - self.auth = auth.AuthResourceWithRawResponse(client.auth) - self.api_keys = api_keys.APIKeysResourceWithRawResponse(client.api_keys) - self.channel_groups = channel_groups.ChannelGroupsResourceWithRawResponse(client.channel_groups) - self.channels = channels.ChannelsResourceWithRawResponse(client.channels) - self.environments = environments.EnvironmentsResourceWithRawResponse(client.environments) - self.variables = variables.VariablesResourceWithRawResponse(client.variables) + self._client = client + + @cached_property + def email_layouts(self) -> email_layouts.EmailLayoutsResourceWithRawResponse: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import EmailLayoutsResourceWithRawResponse + + return EmailLayoutsResourceWithRawResponse(self._client.email_layouts) + + @cached_property + def commits(self) -> commits.CommitsResourceWithRawResponse: + """Commits are versioned changes to resources.""" + from .resources.commits import CommitsResourceWithRawResponse + + return CommitsResourceWithRawResponse(self._client.commits) + + @cached_property + def partials(self) -> partials.PartialsResourceWithRawResponse: + """Partials allow you to reuse content across templates.""" + from .resources.partials import PartialsResourceWithRawResponse + + return PartialsResourceWithRawResponse(self._client.partials) + + @cached_property + def translations(self) -> translations.TranslationsResourceWithRawResponse: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import TranslationsResourceWithRawResponse + + return TranslationsResourceWithRawResponse(self._client.translations) + + @cached_property + def workflows(self) -> workflows.WorkflowsResourceWithRawResponse: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import WorkflowsResourceWithRawResponse + + return WorkflowsResourceWithRawResponse(self._client.workflows) + + @cached_property + def message_types(self) -> message_types.MessageTypesResourceWithRawResponse: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import MessageTypesResourceWithRawResponse + + return MessageTypesResourceWithRawResponse(self._client.message_types) + + @cached_property + def auth(self) -> auth.AuthResourceWithRawResponse: + """Resources for managing your Knock account.""" + from .resources.auth import AuthResourceWithRawResponse + + return AuthResourceWithRawResponse(self._client.auth) + + @cached_property + def api_keys(self) -> api_keys.APIKeysResourceWithRawResponse: + from .resources.api_keys import APIKeysResourceWithRawResponse + + return APIKeysResourceWithRawResponse(self._client.api_keys) + + @cached_property + def channel_groups(self) -> channel_groups.ChannelGroupsResourceWithRawResponse: + from .resources.channel_groups import ChannelGroupsResourceWithRawResponse + + return ChannelGroupsResourceWithRawResponse(self._client.channel_groups) + + @cached_property + def channels(self) -> channels.ChannelsResourceWithRawResponse: + from .resources.channels import ChannelsResourceWithRawResponse + + return ChannelsResourceWithRawResponse(self._client.channels) + + @cached_property + def members(self) -> members.MembersResourceWithRawResponse: + from .resources.members import MembersResourceWithRawResponse + + return MembersResourceWithRawResponse(self._client.members) + + @cached_property + def environments(self) -> environments.EnvironmentsResourceWithRawResponse: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import EnvironmentsResourceWithRawResponse + + return EnvironmentsResourceWithRawResponse(self._client.environments) + + @cached_property + def variables(self) -> variables.VariablesResourceWithRawResponse: + from .resources.variables import VariablesResourceWithRawResponse + + return VariablesResourceWithRawResponse(self._client.variables) + + @cached_property + def guides(self) -> guides.GuidesResourceWithRawResponse: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import GuidesResourceWithRawResponse + + return GuidesResourceWithRawResponse(self._client.guides) + + @cached_property + def branches(self) -> branches.BranchesResourceWithRawResponse: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import BranchesResourceWithRawResponse + + return BranchesResourceWithRawResponse(self._client.branches) + + @cached_property + def broadcasts(self) -> broadcasts.BroadcastsResourceWithRawResponse: + from .resources.broadcasts import BroadcastsResourceWithRawResponse + + return BroadcastsResourceWithRawResponse(self._client.broadcasts) + + @cached_property + def audiences(self) -> audiences.AudiencesResourceWithRawResponse: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AudiencesResourceWithRawResponse + + return AudiencesResourceWithRawResponse(self._client.audiences) class AsyncKnockMgmtWithRawResponse: + _client: AsyncKnockMgmt + def __init__(self, client: AsyncKnockMgmt) -> None: - self.email_layouts = email_layouts.AsyncEmailLayoutsResourceWithRawResponse(client.email_layouts) - self.commits = commits.AsyncCommitsResourceWithRawResponse(client.commits) - self.partials = partials.AsyncPartialsResourceWithRawResponse(client.partials) - self.translations = translations.AsyncTranslationsResourceWithRawResponse(client.translations) - self.workflows = workflows.AsyncWorkflowsResourceWithRawResponse(client.workflows) - self.message_types = message_types.AsyncMessageTypesResourceWithRawResponse(client.message_types) - self.auth = auth.AsyncAuthResourceWithRawResponse(client.auth) - self.api_keys = api_keys.AsyncAPIKeysResourceWithRawResponse(client.api_keys) - self.channel_groups = channel_groups.AsyncChannelGroupsResourceWithRawResponse(client.channel_groups) - self.channels = channels.AsyncChannelsResourceWithRawResponse(client.channels) - self.environments = environments.AsyncEnvironmentsResourceWithRawResponse(client.environments) - self.variables = variables.AsyncVariablesResourceWithRawResponse(client.variables) + self._client = client + + @cached_property + def email_layouts(self) -> email_layouts.AsyncEmailLayoutsResourceWithRawResponse: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import AsyncEmailLayoutsResourceWithRawResponse + + return AsyncEmailLayoutsResourceWithRawResponse(self._client.email_layouts) + + @cached_property + def commits(self) -> commits.AsyncCommitsResourceWithRawResponse: + """Commits are versioned changes to resources.""" + from .resources.commits import AsyncCommitsResourceWithRawResponse + + return AsyncCommitsResourceWithRawResponse(self._client.commits) + + @cached_property + def partials(self) -> partials.AsyncPartialsResourceWithRawResponse: + """Partials allow you to reuse content across templates.""" + from .resources.partials import AsyncPartialsResourceWithRawResponse + + return AsyncPartialsResourceWithRawResponse(self._client.partials) + + @cached_property + def translations(self) -> translations.AsyncTranslationsResourceWithRawResponse: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import AsyncTranslationsResourceWithRawResponse + + return AsyncTranslationsResourceWithRawResponse(self._client.translations) + + @cached_property + def workflows(self) -> workflows.AsyncWorkflowsResourceWithRawResponse: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import AsyncWorkflowsResourceWithRawResponse + + return AsyncWorkflowsResourceWithRawResponse(self._client.workflows) + + @cached_property + def message_types(self) -> message_types.AsyncMessageTypesResourceWithRawResponse: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import AsyncMessageTypesResourceWithRawResponse + + return AsyncMessageTypesResourceWithRawResponse(self._client.message_types) + + @cached_property + def auth(self) -> auth.AsyncAuthResourceWithRawResponse: + """Resources for managing your Knock account.""" + from .resources.auth import AsyncAuthResourceWithRawResponse + + return AsyncAuthResourceWithRawResponse(self._client.auth) + + @cached_property + def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithRawResponse: + from .resources.api_keys import AsyncAPIKeysResourceWithRawResponse + + return AsyncAPIKeysResourceWithRawResponse(self._client.api_keys) + + @cached_property + def channel_groups(self) -> channel_groups.AsyncChannelGroupsResourceWithRawResponse: + from .resources.channel_groups import AsyncChannelGroupsResourceWithRawResponse + + return AsyncChannelGroupsResourceWithRawResponse(self._client.channel_groups) + + @cached_property + def channels(self) -> channels.AsyncChannelsResourceWithRawResponse: + from .resources.channels import AsyncChannelsResourceWithRawResponse + + return AsyncChannelsResourceWithRawResponse(self._client.channels) + + @cached_property + def members(self) -> members.AsyncMembersResourceWithRawResponse: + from .resources.members import AsyncMembersResourceWithRawResponse + + return AsyncMembersResourceWithRawResponse(self._client.members) + + @cached_property + def environments(self) -> environments.AsyncEnvironmentsResourceWithRawResponse: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import AsyncEnvironmentsResourceWithRawResponse + + return AsyncEnvironmentsResourceWithRawResponse(self._client.environments) + + @cached_property + def variables(self) -> variables.AsyncVariablesResourceWithRawResponse: + from .resources.variables import AsyncVariablesResourceWithRawResponse + + return AsyncVariablesResourceWithRawResponse(self._client.variables) + + @cached_property + def guides(self) -> guides.AsyncGuidesResourceWithRawResponse: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import AsyncGuidesResourceWithRawResponse + + return AsyncGuidesResourceWithRawResponse(self._client.guides) + + @cached_property + def branches(self) -> branches.AsyncBranchesResourceWithRawResponse: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import AsyncBranchesResourceWithRawResponse + + return AsyncBranchesResourceWithRawResponse(self._client.branches) + + @cached_property + def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithRawResponse: + from .resources.broadcasts import AsyncBroadcastsResourceWithRawResponse + + return AsyncBroadcastsResourceWithRawResponse(self._client.broadcasts) + + @cached_property + def audiences(self) -> audiences.AsyncAudiencesResourceWithRawResponse: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AsyncAudiencesResourceWithRawResponse + + return AsyncAudiencesResourceWithRawResponse(self._client.audiences) class KnockMgmtWithStreamedResponse: + _client: KnockMgmt + def __init__(self, client: KnockMgmt) -> None: - self.email_layouts = email_layouts.EmailLayoutsResourceWithStreamingResponse(client.email_layouts) - self.commits = commits.CommitsResourceWithStreamingResponse(client.commits) - self.partials = partials.PartialsResourceWithStreamingResponse(client.partials) - self.translations = translations.TranslationsResourceWithStreamingResponse(client.translations) - self.workflows = workflows.WorkflowsResourceWithStreamingResponse(client.workflows) - self.message_types = message_types.MessageTypesResourceWithStreamingResponse(client.message_types) - self.auth = auth.AuthResourceWithStreamingResponse(client.auth) - self.api_keys = api_keys.APIKeysResourceWithStreamingResponse(client.api_keys) - self.channel_groups = channel_groups.ChannelGroupsResourceWithStreamingResponse(client.channel_groups) - self.channels = channels.ChannelsResourceWithStreamingResponse(client.channels) - self.environments = environments.EnvironmentsResourceWithStreamingResponse(client.environments) - self.variables = variables.VariablesResourceWithStreamingResponse(client.variables) + self._client = client + + @cached_property + def email_layouts(self) -> email_layouts.EmailLayoutsResourceWithStreamingResponse: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import EmailLayoutsResourceWithStreamingResponse + + return EmailLayoutsResourceWithStreamingResponse(self._client.email_layouts) + + @cached_property + def commits(self) -> commits.CommitsResourceWithStreamingResponse: + """Commits are versioned changes to resources.""" + from .resources.commits import CommitsResourceWithStreamingResponse + + return CommitsResourceWithStreamingResponse(self._client.commits) + + @cached_property + def partials(self) -> partials.PartialsResourceWithStreamingResponse: + """Partials allow you to reuse content across templates.""" + from .resources.partials import PartialsResourceWithStreamingResponse + + return PartialsResourceWithStreamingResponse(self._client.partials) + + @cached_property + def translations(self) -> translations.TranslationsResourceWithStreamingResponse: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import TranslationsResourceWithStreamingResponse + + return TranslationsResourceWithStreamingResponse(self._client.translations) + + @cached_property + def workflows(self) -> workflows.WorkflowsResourceWithStreamingResponse: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import WorkflowsResourceWithStreamingResponse + + return WorkflowsResourceWithStreamingResponse(self._client.workflows) + + @cached_property + def message_types(self) -> message_types.MessageTypesResourceWithStreamingResponse: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import MessageTypesResourceWithStreamingResponse + + return MessageTypesResourceWithStreamingResponse(self._client.message_types) + + @cached_property + def auth(self) -> auth.AuthResourceWithStreamingResponse: + """Resources for managing your Knock account.""" + from .resources.auth import AuthResourceWithStreamingResponse + + return AuthResourceWithStreamingResponse(self._client.auth) + + @cached_property + def api_keys(self) -> api_keys.APIKeysResourceWithStreamingResponse: + from .resources.api_keys import APIKeysResourceWithStreamingResponse + + return APIKeysResourceWithStreamingResponse(self._client.api_keys) + + @cached_property + def channel_groups(self) -> channel_groups.ChannelGroupsResourceWithStreamingResponse: + from .resources.channel_groups import ChannelGroupsResourceWithStreamingResponse + + return ChannelGroupsResourceWithStreamingResponse(self._client.channel_groups) + + @cached_property + def channels(self) -> channels.ChannelsResourceWithStreamingResponse: + from .resources.channels import ChannelsResourceWithStreamingResponse + + return ChannelsResourceWithStreamingResponse(self._client.channels) + + @cached_property + def members(self) -> members.MembersResourceWithStreamingResponse: + from .resources.members import MembersResourceWithStreamingResponse + + return MembersResourceWithStreamingResponse(self._client.members) + + @cached_property + def environments(self) -> environments.EnvironmentsResourceWithStreamingResponse: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import EnvironmentsResourceWithStreamingResponse + + return EnvironmentsResourceWithStreamingResponse(self._client.environments) + + @cached_property + def variables(self) -> variables.VariablesResourceWithStreamingResponse: + from .resources.variables import VariablesResourceWithStreamingResponse + + return VariablesResourceWithStreamingResponse(self._client.variables) + + @cached_property + def guides(self) -> guides.GuidesResourceWithStreamingResponse: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import GuidesResourceWithStreamingResponse + + return GuidesResourceWithStreamingResponse(self._client.guides) + + @cached_property + def branches(self) -> branches.BranchesResourceWithStreamingResponse: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import BranchesResourceWithStreamingResponse + + return BranchesResourceWithStreamingResponse(self._client.branches) + + @cached_property + def broadcasts(self) -> broadcasts.BroadcastsResourceWithStreamingResponse: + from .resources.broadcasts import BroadcastsResourceWithStreamingResponse + + return BroadcastsResourceWithStreamingResponse(self._client.broadcasts) + + @cached_property + def audiences(self) -> audiences.AudiencesResourceWithStreamingResponse: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AudiencesResourceWithStreamingResponse + + return AudiencesResourceWithStreamingResponse(self._client.audiences) class AsyncKnockMgmtWithStreamedResponse: + _client: AsyncKnockMgmt + def __init__(self, client: AsyncKnockMgmt) -> None: - self.email_layouts = email_layouts.AsyncEmailLayoutsResourceWithStreamingResponse(client.email_layouts) - self.commits = commits.AsyncCommitsResourceWithStreamingResponse(client.commits) - self.partials = partials.AsyncPartialsResourceWithStreamingResponse(client.partials) - self.translations = translations.AsyncTranslationsResourceWithStreamingResponse(client.translations) - self.workflows = workflows.AsyncWorkflowsResourceWithStreamingResponse(client.workflows) - self.message_types = message_types.AsyncMessageTypesResourceWithStreamingResponse(client.message_types) - self.auth = auth.AsyncAuthResourceWithStreamingResponse(client.auth) - self.api_keys = api_keys.AsyncAPIKeysResourceWithStreamingResponse(client.api_keys) - self.channel_groups = channel_groups.AsyncChannelGroupsResourceWithStreamingResponse(client.channel_groups) - self.channels = channels.AsyncChannelsResourceWithStreamingResponse(client.channels) - self.environments = environments.AsyncEnvironmentsResourceWithStreamingResponse(client.environments) - self.variables = variables.AsyncVariablesResourceWithStreamingResponse(client.variables) + self._client = client + + @cached_property + def email_layouts(self) -> email_layouts.AsyncEmailLayoutsResourceWithStreamingResponse: + """Email layouts wrap your email templates and provide a consistent look and feel.""" + from .resources.email_layouts import AsyncEmailLayoutsResourceWithStreamingResponse + + return AsyncEmailLayoutsResourceWithStreamingResponse(self._client.email_layouts) + + @cached_property + def commits(self) -> commits.AsyncCommitsResourceWithStreamingResponse: + """Commits are versioned changes to resources.""" + from .resources.commits import AsyncCommitsResourceWithStreamingResponse + + return AsyncCommitsResourceWithStreamingResponse(self._client.commits) + + @cached_property + def partials(self) -> partials.AsyncPartialsResourceWithStreamingResponse: + """Partials allow you to reuse content across templates.""" + from .resources.partials import AsyncPartialsResourceWithStreamingResponse + + return AsyncPartialsResourceWithStreamingResponse(self._client.partials) + + @cached_property + def translations(self) -> translations.AsyncTranslationsResourceWithStreamingResponse: + """Translations are per-locale string files that can be used in your templates.""" + from .resources.translations import AsyncTranslationsResourceWithStreamingResponse + + return AsyncTranslationsResourceWithStreamingResponse(self._client.translations) + + @cached_property + def workflows(self) -> workflows.AsyncWorkflowsResourceWithStreamingResponse: + """Workflows let you express your cross-channel notification logic.""" + from .resources.workflows import AsyncWorkflowsResourceWithStreamingResponse + + return AsyncWorkflowsResourceWithStreamingResponse(self._client.workflows) + + @cached_property + def message_types(self) -> message_types.AsyncMessageTypesResourceWithStreamingResponse: + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + from .resources.message_types import AsyncMessageTypesResourceWithStreamingResponse + + return AsyncMessageTypesResourceWithStreamingResponse(self._client.message_types) + + @cached_property + def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse: + """Resources for managing your Knock account.""" + from .resources.auth import AsyncAuthResourceWithStreamingResponse + + return AsyncAuthResourceWithStreamingResponse(self._client.auth) + + @cached_property + def api_keys(self) -> api_keys.AsyncAPIKeysResourceWithStreamingResponse: + from .resources.api_keys import AsyncAPIKeysResourceWithStreamingResponse + + return AsyncAPIKeysResourceWithStreamingResponse(self._client.api_keys) + + @cached_property + def channel_groups(self) -> channel_groups.AsyncChannelGroupsResourceWithStreamingResponse: + from .resources.channel_groups import AsyncChannelGroupsResourceWithStreamingResponse + + return AsyncChannelGroupsResourceWithStreamingResponse(self._client.channel_groups) + + @cached_property + def channels(self) -> channels.AsyncChannelsResourceWithStreamingResponse: + from .resources.channels import AsyncChannelsResourceWithStreamingResponse + + return AsyncChannelsResourceWithStreamingResponse(self._client.channels) + + @cached_property + def members(self) -> members.AsyncMembersResourceWithStreamingResponse: + from .resources.members import AsyncMembersResourceWithStreamingResponse + + return AsyncMembersResourceWithStreamingResponse(self._client.members) + + @cached_property + def environments(self) -> environments.AsyncEnvironmentsResourceWithStreamingResponse: + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + from .resources.environments import AsyncEnvironmentsResourceWithStreamingResponse + + return AsyncEnvironmentsResourceWithStreamingResponse(self._client.environments) + + @cached_property + def variables(self) -> variables.AsyncVariablesResourceWithStreamingResponse: + from .resources.variables import AsyncVariablesResourceWithStreamingResponse + + return AsyncVariablesResourceWithStreamingResponse(self._client.variables) + + @cached_property + def guides(self) -> guides.AsyncGuidesResourceWithStreamingResponse: + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + from .resources.guides import AsyncGuidesResourceWithStreamingResponse + + return AsyncGuidesResourceWithStreamingResponse(self._client.guides) + + @cached_property + def branches(self) -> branches.AsyncBranchesResourceWithStreamingResponse: + """Branches in Knock are a way to isolate changes to your Knock resources.""" + from .resources.branches import AsyncBranchesResourceWithStreamingResponse + + return AsyncBranchesResourceWithStreamingResponse(self._client.branches) + + @cached_property + def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithStreamingResponse: + from .resources.broadcasts import AsyncBroadcastsResourceWithStreamingResponse + + return AsyncBroadcastsResourceWithStreamingResponse(self._client.broadcasts) + + @cached_property + def audiences(self) -> audiences.AsyncAudiencesResourceWithStreamingResponse: + """Audiences define sets of users that can be targeted for notifications.""" + from .resources.audiences import AsyncAudiencesResourceWithStreamingResponse + + return AsyncAudiencesResourceWithStreamingResponse(self._client.audiences) Client = KnockMgmt diff --git a/src/knock_mapi/_compat.py b/src/knock_mapi/_compat.py index 92d9ee61..786ff42a 100644 --- a/src/knock_mapi/_compat.py +++ b/src/knock_mapi/_compat.py @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( @@ -138,30 +139,30 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: - if PYDANTIC_V2 or hasattr(model, "model_dump"): + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 - warnings=warnings if PYDANTIC_V2 else True, + warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models @@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: diff --git a/src/knock_mapi/_files.py b/src/knock_mapi/_files.py index 715cc207..cc14c14f 100644 --- a/src/knock_mapi/_files.py +++ b/src/knock_mapi/_files.py @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() diff --git a/src/knock_mapi/_models.py b/src/knock_mapi/_models.py index 798956f1..29070e05 100644 --- a/src/knock_mapi/_models.py +++ b/src/knock_mapi/_models.py @@ -2,9 +2,24 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +import weakref +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -49,7 +64,7 @@ strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -80,11 +95,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - else: + if PYDANTIC_V1: @property @override @@ -94,6 +105,10 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) def to_dict( self, @@ -207,28 +222,32 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] else: fields_values[name] = field_get_default(field) + extra_field_type = _get_extra_fields_type(__cls) + _extra = {} for key, value in values.items(): if key not in model_fields: - if PYDANTIC_V2: - _extra[key] = value - else: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: _fields_set.add(key) - fields_values[key] = value + fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -238,7 +257,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -251,13 +270,15 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -266,16 +287,24 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. @@ -290,31 +319,38 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( self, *, indent: int | None = None, + ensure_ascii: bool = False, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + context: Any | None = None, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -343,11 +379,17 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -358,15 +400,32 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None def is_basemodel(type_: type) -> bool: @@ -420,7 +479,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +497,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() @@ -543,6 +604,9 @@ class CachedDiscriminatorType(Protocol): __discriminator__: DiscriminatorDetails +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + class DiscriminatorDetails: field_name: str """The name of the discriminator field in the variant class, e.g. @@ -585,8 +649,9 @@ def __init__( def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: - if isinstance(union, CachedDiscriminatorType): - return union.__discriminator__ + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached discriminator_field_name: str | None = None @@ -604,30 +669,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, for variant in get_args(union): variant = strip_annotated_type(variant) if is_basemodel_type(variant): - if PYDANTIC_V2: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant else: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias + discriminator_alias = field.get("serialization_alias") - if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): - for entry in get_args(annotation): + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -639,7 +704,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, discriminator_field=discriminator_field_name, discriminator_alias=discriminator_alias, ) - cast(CachedDiscriminatorType, union).__discriminator__ = details + DISCRIMINATOR_CACHE.setdefault(union, details) return details @@ -690,7 +755,7 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: +if not PYDANTIC_V1: from pydantic import TypeAdapter as _TypeAdapter _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) @@ -735,8 +800,10 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,18 +817,20 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -794,9 +863,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/knock_mapi/_qs.py b/src/knock_mapi/_qs.py index 274320ca..ada6fd3f 100644 --- a/src/knock_mapi/_qs.py +++ b/src/knock_mapi/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/knock_mapi/_response.py b/src/knock_mapi/_response.py index c1c3c3d3..ca02cbdb 100644 --- a/src/knock_mapi/_response.py +++ b/src/knock_mapi/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/knock_mapi/_streaming.py b/src/knock_mapi/_streaming.py index 43b7d54c..0be478f2 100644 --- a/src/knock_mapi/_streaming.py +++ b/src/knock_mapi/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import KnockMgmt, AsyncKnockMgmt + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: KnockMgmt, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -54,12 +57,12 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - for _sse in iterator: - ... + try: + for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncKnockMgmt, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -118,12 +123,12 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + try: + async for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self diff --git a/src/knock_mapi/_types.py b/src/knock_mapi/_types.py index d27367cf..ac2c2a42 100644 --- a/src/knock_mapi/_types.py +++ b/src/knock_mapi/_types.py @@ -13,10 +13,23 @@ Mapping, TypeVar, Callable, + Iterable, + Iterator, Optional, Sequence, + AsyncIterable, +) +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, ) -from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable import httpx import pydantic @@ -45,6 +58,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, @@ -100,23 +120,27 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -128,13 +152,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -144,8 +169,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -153,6 +178,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod @@ -215,3 +243,28 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/knock_mapi/_utils/__init__.py b/src/knock_mapi/_utils/__init__.py index d4fda26f..dc64e29a 100644 --- a/src/knock_mapi/_utils/__init__.py +++ b/src/knock_mapi/_utils/__init__.py @@ -10,7 +10,6 @@ lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -23,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -32,12 +30,20 @@ maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, ) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) from ._typing import ( is_list_type as is_list_type, is_union_type as is_union_type, extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, @@ -55,3 +61,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/knock_mapi/_utils/_compat.py b/src/knock_mapi/_utils/_compat.py new file mode 100644 index 00000000..2c70b299 --- /dev/null +++ b/src/knock_mapi/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/knock_mapi/_utils/_datetime_parse.py b/src/knock_mapi/_utils/_datetime_parse.py new file mode 100644 index 00000000..7cb9d9e6 --- /dev/null +++ b/src/knock_mapi/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/knock_mapi/_utils/_json.py b/src/knock_mapi/_utils/_json.py new file mode 100644 index 00000000..60584214 --- /dev/null +++ b/src/knock_mapi/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/knock_mapi/_utils/_proxy.py b/src/knock_mapi/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/knock_mapi/_utils/_proxy.py +++ b/src/knock_mapi/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/src/knock_mapi/_utils/_resources_proxy.py b/src/knock_mapi/_utils/_resources_proxy.py new file mode 100644 index 00000000..8918f2ea --- /dev/null +++ b/src/knock_mapi/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `knock_mapi.resources` module. + + This is used so that we can lazily import `knock_mapi.resources` only when + needed *and* so that users can just import `knock_mapi` and reference `knock_mapi.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("knock_mapi.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/knock_mapi/_utils/_sync.py b/src/knock_mapi/_utils/_sync.py index ad7ec71b..f6027c18 100644 --- a/src/knock_mapi/_utils/_sync.py +++ b/src/knock_mapi/_utils/_sync.py @@ -1,10 +1,8 @@ from __future__ import annotations -import sys import asyncio import functools -import contextvars -from typing import Any, TypeVar, Callable, Awaitable +from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio @@ -15,34 +13,11 @@ T_ParamSpec = ParamSpec("T_ParamSpec") -if sys.version_info >= (3, 9): - _asyncio_to_thread = asyncio.to_thread -else: - # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread - # for Python 3.8 support - async def _asyncio_to_thread( - func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs - ) -> Any: - """Asynchronously run function *func* in a separate thread. - - Any *args and **kwargs supplied for this function are directly passed - to *func*. Also, the current :class:`contextvars.Context` is propagated, - allowing context variables from the main thread to be accessed in the - separate thread. - - Returns a coroutine that can be awaited to get the eventual result of *func*. - """ - loop = asyncio.events.get_running_loop() - ctx = contextvars.copy_context() - func_call = functools.partial(ctx.run, func, *args, **kwargs) - return await loop.run_in_executor(None, func_call) - - async def to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> T_Retval: if sniffio.current_async_library() == "asyncio": - return await _asyncio_to_thread(func, *args, **kwargs) + return await asyncio.to_thread(func, *args, **kwargs) return await anyio.to_thread.run_sync( functools.partial(func, *args, **kwargs), @@ -53,10 +28,7 @@ async def to_thread( def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments. For python version 3.9 and above, it uses - asyncio.to_thread to run the function in a separate thread. For python version - 3.8, it uses locally defined copy of the asyncio.to_thread function which was - introduced in python 3.9. + positional and keyword arguments. Usage: diff --git a/src/knock_mapi/_utils/_transform.py b/src/knock_mapi/_utils/_transform.py index b0cc20a7..52075492 100644 --- a/src/knock_mapi/_utils/_transform.py +++ b/src/knock_mapi/_utils/_transform.py @@ -16,18 +16,20 @@ lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) -from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -167,6 +169,8 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation @@ -184,6 +188,8 @@ def _transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -262,7 +268,7 @@ def _transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue @@ -329,6 +335,8 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation @@ -346,6 +354,8 @@ async def _async_transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -424,7 +434,7 @@ async def _async_transform_typeddict( annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): if not is_given(value): - # we don't need to include `NotGiven` values here as they'll + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue diff --git a/src/knock_mapi/_utils/_typing.py b/src/knock_mapi/_utils/_typing.py index 1bac9542..193109f3 100644 --- a/src/knock_mapi/_utils/_typing.py +++ b/src/knock_mapi/_utils/_typing.py @@ -15,7 +15,7 @@ from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: @@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ diff --git a/src/knock_mapi/_utils/_utils.py b/src/knock_mapi/_utils/_utils.py index ea3cf3f2..eec7f4a1 100644 --- a/src/knock_mapi/_utils/_utils.py +++ b/src/knock_mapi/_utils/_utils.py @@ -21,8 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -64,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -127,14 +126,14 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. # The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], # however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in it's place. +# care about the contained types we can safely use `object` in its place. # # There are two separate functions defined, `is_*` and `is_*_t` for different use cases. # `is_*` is for when you're dealing with an unknown input diff --git a/src/knock_mapi/_version.py b/src/knock_mapi/_version.py index a7e5bf1b..55486129 100644 --- a/src/knock_mapi/_version.py +++ b/src/knock_mapi/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "knock_mapi" -__version__ = "0.0.1-alpha.0" +__version__ = "0.1.0-alpha.1" # x-release-please-version diff --git a/src/knock_mapi/resources/__init__.py b/src/knock_mapi/resources/__init__.py index 921529cf..1ee50965 100644 --- a/src/knock_mapi/resources/__init__.py +++ b/src/knock_mapi/resources/__init__.py @@ -8,6 +8,14 @@ AuthResourceWithStreamingResponse, AsyncAuthResourceWithStreamingResponse, ) +from .guides import ( + GuidesResource, + AsyncGuidesResource, + GuidesResourceWithRawResponse, + AsyncGuidesResourceWithRawResponse, + GuidesResourceWithStreamingResponse, + AsyncGuidesResourceWithStreamingResponse, +) from .commits import ( CommitsResource, AsyncCommitsResource, @@ -16,6 +24,14 @@ CommitsResourceWithStreamingResponse, AsyncCommitsResourceWithStreamingResponse, ) +from .members import ( + MembersResource, + AsyncMembersResource, + MembersResourceWithRawResponse, + AsyncMembersResourceWithRawResponse, + MembersResourceWithStreamingResponse, + AsyncMembersResourceWithStreamingResponse, +) from .api_keys import ( APIKeysResource, AsyncAPIKeysResource, @@ -24,6 +40,14 @@ APIKeysResourceWithStreamingResponse, AsyncAPIKeysResourceWithStreamingResponse, ) +from .branches import ( + BranchesResource, + AsyncBranchesResource, + BranchesResourceWithRawResponse, + AsyncBranchesResourceWithRawResponse, + BranchesResourceWithStreamingResponse, + AsyncBranchesResourceWithStreamingResponse, +) from .channels import ( ChannelsResource, AsyncChannelsResource, @@ -40,6 +64,14 @@ PartialsResourceWithStreamingResponse, AsyncPartialsResourceWithStreamingResponse, ) +from .audiences import ( + AudiencesResource, + AsyncAudiencesResource, + AudiencesResourceWithRawResponse, + AsyncAudiencesResourceWithRawResponse, + AudiencesResourceWithStreamingResponse, + AsyncAudiencesResourceWithStreamingResponse, +) from .variables import ( VariablesResource, AsyncVariablesResource, @@ -56,6 +88,14 @@ WorkflowsResourceWithStreamingResponse, AsyncWorkflowsResourceWithStreamingResponse, ) +from .broadcasts import ( + BroadcastsResource, + AsyncBroadcastsResource, + BroadcastsResourceWithRawResponse, + AsyncBroadcastsResourceWithRawResponse, + BroadcastsResourceWithStreamingResponse, + AsyncBroadcastsResourceWithStreamingResponse, +) from .environments import ( EnvironmentsResource, AsyncEnvironmentsResource, @@ -158,6 +198,12 @@ "AsyncChannelsResourceWithRawResponse", "ChannelsResourceWithStreamingResponse", "AsyncChannelsResourceWithStreamingResponse", + "MembersResource", + "AsyncMembersResource", + "MembersResourceWithRawResponse", + "AsyncMembersResourceWithRawResponse", + "MembersResourceWithStreamingResponse", + "AsyncMembersResourceWithStreamingResponse", "EnvironmentsResource", "AsyncEnvironmentsResource", "EnvironmentsResourceWithRawResponse", @@ -170,4 +216,28 @@ "AsyncVariablesResourceWithRawResponse", "VariablesResourceWithStreamingResponse", "AsyncVariablesResourceWithStreamingResponse", + "GuidesResource", + "AsyncGuidesResource", + "GuidesResourceWithRawResponse", + "AsyncGuidesResourceWithRawResponse", + "GuidesResourceWithStreamingResponse", + "AsyncGuidesResourceWithStreamingResponse", + "BranchesResource", + "AsyncBranchesResource", + "BranchesResourceWithRawResponse", + "AsyncBranchesResourceWithRawResponse", + "BranchesResourceWithStreamingResponse", + "AsyncBranchesResourceWithStreamingResponse", + "BroadcastsResource", + "AsyncBroadcastsResource", + "BroadcastsResourceWithRawResponse", + "AsyncBroadcastsResourceWithRawResponse", + "BroadcastsResourceWithStreamingResponse", + "AsyncBroadcastsResourceWithStreamingResponse", + "AudiencesResource", + "AsyncAudiencesResource", + "AudiencesResourceWithRawResponse", + "AsyncAudiencesResourceWithRawResponse", + "AudiencesResourceWithStreamingResponse", + "AsyncAudiencesResourceWithStreamingResponse", ] diff --git a/src/knock_mapi/resources/api_keys.py b/src/knock_mapi/resources/api_keys.py index 7dd93b10..93a2874e 100644 --- a/src/knock_mapi/resources/api_keys.py +++ b/src/knock_mapi/resources/api_keys.py @@ -5,7 +5,7 @@ import httpx from ..types import api_key_exchange_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -28,7 +28,7 @@ def with_raw_response(self) -> APIKeysResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return APIKeysResourceWithRawResponse(self) @@ -37,7 +37,7 @@ def with_streaming_response(self) -> APIKeysResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return APIKeysResourceWithStreamingResponse(self) @@ -50,7 +50,7 @@ def exchange( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIKeyExchangeResponse: """ Given an authenticated service token and an environment, will exchange the @@ -88,7 +88,7 @@ def with_raw_response(self) -> AsyncAPIKeysResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncAPIKeysResourceWithRawResponse(self) @@ -97,7 +97,7 @@ def with_streaming_response(self) -> AsyncAPIKeysResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncAPIKeysResourceWithStreamingResponse(self) @@ -110,7 +110,7 @@ async def exchange( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIKeyExchangeResponse: """ Given an authenticated service token and an environment, will exchange the diff --git a/src/knock_mapi/resources/audiences.py b/src/knock_mapi/resources/audiences.py new file mode 100644 index 00000000..2e11f50d --- /dev/null +++ b/src/knock_mapi/resources/audiences.py @@ -0,0 +1,757 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, cast + +import httpx + +from ..types import ( + audience_list_params, + audience_upsert_params, + audience_archive_params, + audience_retrieve_params, + audience_validate_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncEntriesCursor, AsyncEntriesCursor +from .._base_client import AsyncPaginator, make_request_options +from ..types.audience import Audience +from ..types.audience_upsert_response import AudienceUpsertResponse +from ..types.audience_archive_response import AudienceArchiveResponse +from ..types.audience_validate_response import AudienceValidateResponse + +__all__ = ["AudiencesResource", "AsyncAudiencesResource"] + + +class AudiencesResource(SyncAPIResource): + """Audiences define sets of users that can be targeted for notifications.""" + + @cached_property + def with_raw_response(self) -> AudiencesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AudiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AudiencesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AudiencesResourceWithStreamingResponse(self) + + def retrieve( + self, + audience_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Audience: + """ + Retrieve an audience by its key in a given environment. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return cast( + Audience, + self._get( + f"/v1/audiences/{audience_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + audience_retrieve_params.AudienceRetrieveParams, + ), + ), + cast_to=cast(Any, Audience), # Union types cannot be passed in as arguments in the type system + ), + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncEntriesCursor[Audience]: + """ + Returns a paginated list of audiences for the given environment. + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/audiences", + page=SyncEntriesCursor[Audience], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + audience_list_params.AudienceListParams, + ), + ), + model=cast(Any, Audience), # Union types cannot be passed in as arguments in the type system + ) + + def archive( + self, + audience_key: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceArchiveResponse: + """ + Archives a given audience across all environments. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return self._delete( + f"/v1/audiences/{audience_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"environment": environment}, audience_archive_params.AudienceArchiveParams), + ), + cast_to=AudienceArchiveResponse, + ) + + def upsert( + self, + audience_key: str, + *, + environment: str, + audience: audience_upsert_params.Audience, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceUpsertResponse: + """ + Updates an audience of a given key, or creates a new one if it does not yet + exist. + + Args: + environment: The environment slug. + + audience: An audience object with attributes to create or update an audience. Use + `type: static` for audiences with explicitly managed members, or `type: dynamic` + for audiences with segment-based membership. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + commit: Whether to commit the resource at the same time as modifying it. + + commit_message: The message to commit the resource with, only used if `commit` is `true`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return self._put( + f"/v1/audiences/{audience_key}", + body=maybe_transform({"audience": audience}, audience_upsert_params.AudienceUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "commit": commit, + "commit_message": commit_message, + }, + audience_upsert_params.AudienceUpsertParams, + ), + ), + cast_to=AudienceUpsertResponse, + ) + + def validate( + self, + audience_key: str, + *, + environment: str, + audience: audience_validate_params.Audience, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceValidateResponse: + """ + Validates an audience payload without persisting it. + + Args: + environment: The environment slug. + + audience: An audience object with attributes to create or update an audience. Use + `type: static` for audiences with explicitly managed members, or `type: dynamic` + for audiences with segment-based membership. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return self._put( + f"/v1/audiences/{audience_key}/validate", + body=maybe_transform({"audience": audience}, audience_validate_params.AudienceValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + audience_validate_params.AudienceValidateParams, + ), + ), + cast_to=AudienceValidateResponse, + ) + + +class AsyncAudiencesResource(AsyncAPIResource): + """Audiences define sets of users that can be targeted for notifications.""" + + @cached_property + def with_raw_response(self) -> AsyncAudiencesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AsyncAudiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAudiencesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AsyncAudiencesResourceWithStreamingResponse(self) + + async def retrieve( + self, + audience_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Audience: + """ + Retrieve an audience by its key in a given environment. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return cast( + Audience, + await self._get( + f"/v1/audiences/{audience_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + audience_retrieve_params.AudienceRetrieveParams, + ), + ), + cast_to=cast(Any, Audience), # Union types cannot be passed in as arguments in the type system + ), + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Audience, AsyncEntriesCursor[Audience]]: + """ + Returns a paginated list of audiences for the given environment. + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/audiences", + page=AsyncEntriesCursor[Audience], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + audience_list_params.AudienceListParams, + ), + ), + model=cast(Any, Audience), # Union types cannot be passed in as arguments in the type system + ) + + async def archive( + self, + audience_key: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceArchiveResponse: + """ + Archives a given audience across all environments. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return await self._delete( + f"/v1/audiences/{audience_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"environment": environment}, audience_archive_params.AudienceArchiveParams + ), + ), + cast_to=AudienceArchiveResponse, + ) + + async def upsert( + self, + audience_key: str, + *, + environment: str, + audience: audience_upsert_params.Audience, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceUpsertResponse: + """ + Updates an audience of a given key, or creates a new one if it does not yet + exist. + + Args: + environment: The environment slug. + + audience: An audience object with attributes to create or update an audience. Use + `type: static` for audiences with explicitly managed members, or `type: dynamic` + for audiences with segment-based membership. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + commit: Whether to commit the resource at the same time as modifying it. + + commit_message: The message to commit the resource with, only used if `commit` is `true`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return await self._put( + f"/v1/audiences/{audience_key}", + body=await async_maybe_transform({"audience": audience}, audience_upsert_params.AudienceUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "commit": commit, + "commit_message": commit_message, + }, + audience_upsert_params.AudienceUpsertParams, + ), + ), + cast_to=AudienceUpsertResponse, + ) + + async def validate( + self, + audience_key: str, + *, + environment: str, + audience: audience_validate_params.Audience, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AudienceValidateResponse: + """ + Validates an audience payload without persisting it. + + Args: + environment: The environment slug. + + audience: An audience object with attributes to create or update an audience. Use + `type: static` for audiences with explicitly managed members, or `type: dynamic` + for audiences with segment-based membership. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not audience_key: + raise ValueError(f"Expected a non-empty value for `audience_key` but received {audience_key!r}") + return await self._put( + f"/v1/audiences/{audience_key}/validate", + body=await async_maybe_transform({"audience": audience}, audience_validate_params.AudienceValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + audience_validate_params.AudienceValidateParams, + ), + ), + cast_to=AudienceValidateResponse, + ) + + +class AudiencesResourceWithRawResponse: + def __init__(self, audiences: AudiencesResource) -> None: + self._audiences = audiences + + self.retrieve = to_raw_response_wrapper( + audiences.retrieve, + ) + self.list = to_raw_response_wrapper( + audiences.list, + ) + self.archive = to_raw_response_wrapper( + audiences.archive, + ) + self.upsert = to_raw_response_wrapper( + audiences.upsert, + ) + self.validate = to_raw_response_wrapper( + audiences.validate, + ) + + +class AsyncAudiencesResourceWithRawResponse: + def __init__(self, audiences: AsyncAudiencesResource) -> None: + self._audiences = audiences + + self.retrieve = async_to_raw_response_wrapper( + audiences.retrieve, + ) + self.list = async_to_raw_response_wrapper( + audiences.list, + ) + self.archive = async_to_raw_response_wrapper( + audiences.archive, + ) + self.upsert = async_to_raw_response_wrapper( + audiences.upsert, + ) + self.validate = async_to_raw_response_wrapper( + audiences.validate, + ) + + +class AudiencesResourceWithStreamingResponse: + def __init__(self, audiences: AudiencesResource) -> None: + self._audiences = audiences + + self.retrieve = to_streamed_response_wrapper( + audiences.retrieve, + ) + self.list = to_streamed_response_wrapper( + audiences.list, + ) + self.archive = to_streamed_response_wrapper( + audiences.archive, + ) + self.upsert = to_streamed_response_wrapper( + audiences.upsert, + ) + self.validate = to_streamed_response_wrapper( + audiences.validate, + ) + + +class AsyncAudiencesResourceWithStreamingResponse: + def __init__(self, audiences: AsyncAudiencesResource) -> None: + self._audiences = audiences + + self.retrieve = async_to_streamed_response_wrapper( + audiences.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + audiences.list, + ) + self.archive = async_to_streamed_response_wrapper( + audiences.archive, + ) + self.upsert = async_to_streamed_response_wrapper( + audiences.upsert, + ) + self.validate = async_to_streamed_response_wrapper( + audiences.validate, + ) diff --git a/src/knock_mapi/resources/auth.py b/src/knock_mapi/resources/auth.py index 83e278cf..65b0c223 100644 --- a/src/knock_mapi/resources/auth.py +++ b/src/knock_mapi/resources/auth.py @@ -4,7 +4,7 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -20,13 +20,15 @@ class AuthResource(SyncAPIResource): + """Resources for managing your Knock account.""" + @cached_property def with_raw_response(self) -> AuthResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AuthResourceWithRawResponse(self) @@ -35,7 +37,7 @@ def with_streaming_response(self) -> AuthResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AuthResourceWithStreamingResponse(self) @@ -47,9 +49,13 @@ def verify( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthVerifyResponse: - """Return information about the current service token.""" + """Return information about the current calling scope. + + Will either be a service + token or from an OAuth context. + """ return self._get( "/v1/whoami", options=make_request_options( @@ -60,13 +66,15 @@ def verify( class AsyncAuthResource(AsyncAPIResource): + """Resources for managing your Knock account.""" + @cached_property def with_raw_response(self) -> AsyncAuthResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncAuthResourceWithRawResponse(self) @@ -75,7 +83,7 @@ def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncAuthResourceWithStreamingResponse(self) @@ -87,9 +95,13 @@ async def verify( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AuthVerifyResponse: - """Return information about the current service token.""" + """Return information about the current calling scope. + + Will either be a service + token or from an OAuth context. + """ return await self._get( "/v1/whoami", options=make_request_options( diff --git a/src/knock_mapi/resources/branches.py b/src/knock_mapi/resources/branches.py new file mode 100644 index 00000000..e64518f8 --- /dev/null +++ b/src/knock_mapi/resources/branches.py @@ -0,0 +1,502 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import branch_list_params, branch_create_params, branch_delete_params, branch_retrieve_params +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncEntriesCursor, AsyncEntriesCursor +from .._base_client import AsyncPaginator, make_request_options +from ..types.branch import Branch + +__all__ = ["BranchesResource", "AsyncBranchesResource"] + + +class BranchesResource(SyncAPIResource): + """Branches in Knock are a way to isolate changes to your Knock resources.""" + + @cached_property + def with_raw_response(self) -> BranchesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return BranchesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BranchesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return BranchesResourceWithStreamingResponse(self) + + def create( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Branch: + """ + Creates a new branch off of the development environment with the given slug. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + return self._post( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"environment": environment}, branch_create_params.BranchCreateParams), + ), + cast_to=Branch, + ) + + def retrieve( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Branch: + """ + Returns a single branch by the `branch_slug`. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + return self._get( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"environment": environment}, branch_retrieve_params.BranchRetrieveParams), + ), + cast_to=Branch, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncEntriesCursor[Branch]: + """Returns a paginated list of branches. + + The branches will be returned in order of + their last commit time (newest first). + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + before: The cursor to fetch entries before. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/branches", + page=SyncEntriesCursor[Branch], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "before": before, + "limit": limit, + }, + branch_list_params.BranchListParams, + ), + ), + model=Branch, + ) + + def delete( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a branch by the `branch_slug`. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"environment": environment}, branch_delete_params.BranchDeleteParams), + ), + cast_to=NoneType, + ) + + +class AsyncBranchesResource(AsyncAPIResource): + """Branches in Knock are a way to isolate changes to your Knock resources.""" + + @cached_property + def with_raw_response(self) -> AsyncBranchesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AsyncBranchesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBranchesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AsyncBranchesResourceWithStreamingResponse(self) + + async def create( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Branch: + """ + Creates a new branch off of the development environment with the given slug. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + return await self._post( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"environment": environment}, branch_create_params.BranchCreateParams + ), + ), + cast_to=Branch, + ) + + async def retrieve( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Branch: + """ + Returns a single branch by the `branch_slug`. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + return await self._get( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"environment": environment}, branch_retrieve_params.BranchRetrieveParams + ), + ), + cast_to=Branch, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Branch, AsyncEntriesCursor[Branch]]: + """Returns a paginated list of branches. + + The branches will be returned in order of + their last commit time (newest first). + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + before: The cursor to fetch entries before. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/branches", + page=AsyncEntriesCursor[Branch], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "before": before, + "limit": limit, + }, + branch_list_params.BranchListParams, + ), + ), + model=Branch, + ) + + async def delete( + self, + branch_slug: str, + *, + environment: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Deletes a branch by the `branch_slug`. + + Args: + environment: The environment slug. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not branch_slug: + raise ValueError(f"Expected a non-empty value for `branch_slug` but received {branch_slug!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/v1/branches/{branch_slug}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"environment": environment}, branch_delete_params.BranchDeleteParams + ), + ), + cast_to=NoneType, + ) + + +class BranchesResourceWithRawResponse: + def __init__(self, branches: BranchesResource) -> None: + self._branches = branches + + self.create = to_raw_response_wrapper( + branches.create, + ) + self.retrieve = to_raw_response_wrapper( + branches.retrieve, + ) + self.list = to_raw_response_wrapper( + branches.list, + ) + self.delete = to_raw_response_wrapper( + branches.delete, + ) + + +class AsyncBranchesResourceWithRawResponse: + def __init__(self, branches: AsyncBranchesResource) -> None: + self._branches = branches + + self.create = async_to_raw_response_wrapper( + branches.create, + ) + self.retrieve = async_to_raw_response_wrapper( + branches.retrieve, + ) + self.list = async_to_raw_response_wrapper( + branches.list, + ) + self.delete = async_to_raw_response_wrapper( + branches.delete, + ) + + +class BranchesResourceWithStreamingResponse: + def __init__(self, branches: BranchesResource) -> None: + self._branches = branches + + self.create = to_streamed_response_wrapper( + branches.create, + ) + self.retrieve = to_streamed_response_wrapper( + branches.retrieve, + ) + self.list = to_streamed_response_wrapper( + branches.list, + ) + self.delete = to_streamed_response_wrapper( + branches.delete, + ) + + +class AsyncBranchesResourceWithStreamingResponse: + def __init__(self, branches: AsyncBranchesResource) -> None: + self._branches = branches + + self.create = async_to_streamed_response_wrapper( + branches.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + branches.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + branches.list, + ) + self.delete = async_to_streamed_response_wrapper( + branches.delete, + ) diff --git a/src/knock_mapi/resources/broadcasts.py b/src/knock_mapi/resources/broadcasts.py new file mode 100644 index 00000000..abdc4958 --- /dev/null +++ b/src/knock_mapi/resources/broadcasts.py @@ -0,0 +1,879 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime + +import httpx + +from ..types import ( + broadcast_list_params, + broadcast_send_params, + broadcast_cancel_params, + broadcast_upsert_params, + broadcast_retrieve_params, + broadcast_validate_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncEntriesCursor, AsyncEntriesCursor +from .._base_client import AsyncPaginator, make_request_options +from ..types.broadcast import Broadcast +from ..types.broadcast_request_param import BroadcastRequestParam +from ..types.broadcast_send_response import BroadcastSendResponse +from ..types.broadcast_cancel_response import BroadcastCancelResponse +from ..types.broadcast_upsert_response import BroadcastUpsertResponse +from ..types.broadcast_validate_response import BroadcastValidateResponse + +__all__ = ["BroadcastsResource", "AsyncBroadcastsResource"] + + +class BroadcastsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BroadcastsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return BroadcastsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BroadcastsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return BroadcastsResourceWithStreamingResponse(self) + + def retrieve( + self, + broadcast_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Broadcast: + """ + Get a broadcast by its key in a given environment. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return self._get( + f"/v1/broadcasts/{broadcast_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + broadcast_retrieve_params.BroadcastRetrieveParams, + ), + ), + cast_to=Broadcast, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncEntriesCursor[Broadcast]: + """Returns a paginated list of broadcasts available in a given environment. + + The + broadcasts are returned ordered by creation time (newest first). + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/broadcasts", + page=SyncEntriesCursor[Broadcast], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + broadcast_list_params.BroadcastListParams, + ), + ), + model=Broadcast, + ) + + def cancel( + self, + broadcast_key: str, + *, + environment: str, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastCancelResponse: + """Cancels sending a scheduled broadcast. + + The broadcast will return to draft + status. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return self._put( + f"/v1/broadcasts/{broadcast_key}/cancel", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_cancel_params.BroadcastCancelParams, + ), + ), + cast_to=BroadcastCancelResponse, + ) + + def send( + self, + broadcast_key: str, + *, + environment: str, + branch: str | Omit = omit, + send_at: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastSendResponse: + """ + Sends a broadcast immediately or schedules it to send at a future time. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + send_at: When to send the broadcast. If provided, the broadcast will be scheduled to send + at this time. Must be in ISO 8601 UTC format. If not provided, the broadcast + will be sent immediately. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return self._put( + f"/v1/broadcasts/{broadcast_key}/send", + body=maybe_transform({"send_at": send_at}, broadcast_send_params.BroadcastSendParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_send_params.BroadcastSendParams, + ), + ), + cast_to=BroadcastSendResponse, + ) + + def upsert( + self, + broadcast_key: str, + *, + environment: str, + broadcast: BroadcastRequestParam, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastUpsertResponse: + """ + Updates a broadcast of a given key, or creates a new one if it does not yet + exist. + + Args: + environment: The environment slug. + + broadcast: A broadcast request for upserting a broadcast. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return self._put( + f"/v1/broadcasts/{broadcast_key}", + body=maybe_transform({"broadcast": broadcast}, broadcast_upsert_params.BroadcastUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + }, + broadcast_upsert_params.BroadcastUpsertParams, + ), + ), + cast_to=BroadcastUpsertResponse, + ) + + def validate( + self, + broadcast_key: str, + *, + environment: str, + broadcast: BroadcastRequestParam, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastValidateResponse: + """ + Validates a broadcast payload without persisting it. + + Args: + environment: The environment slug. + + broadcast: A broadcast request for upserting a broadcast. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return self._put( + f"/v1/broadcasts/{broadcast_key}/validate", + body=maybe_transform({"broadcast": broadcast}, broadcast_validate_params.BroadcastValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_validate_params.BroadcastValidateParams, + ), + ), + cast_to=BroadcastValidateResponse, + ) + + +class AsyncBroadcastsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBroadcastsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AsyncBroadcastsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBroadcastsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AsyncBroadcastsResourceWithStreamingResponse(self) + + async def retrieve( + self, + broadcast_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Broadcast: + """ + Get a broadcast by its key in a given environment. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return await self._get( + f"/v1/broadcasts/{broadcast_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + broadcast_retrieve_params.BroadcastRetrieveParams, + ), + ), + cast_to=Broadcast, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Broadcast, AsyncEntriesCursor[Broadcast]]: + """Returns a paginated list of broadcasts available in a given environment. + + The + broadcasts are returned ordered by creation time (newest first). + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/broadcasts", + page=AsyncEntriesCursor[Broadcast], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + broadcast_list_params.BroadcastListParams, + ), + ), + model=Broadcast, + ) + + async def cancel( + self, + broadcast_key: str, + *, + environment: str, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastCancelResponse: + """Cancels sending a scheduled broadcast. + + The broadcast will return to draft + status. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return await self._put( + f"/v1/broadcasts/{broadcast_key}/cancel", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_cancel_params.BroadcastCancelParams, + ), + ), + cast_to=BroadcastCancelResponse, + ) + + async def send( + self, + broadcast_key: str, + *, + environment: str, + branch: str | Omit = omit, + send_at: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastSendResponse: + """ + Sends a broadcast immediately or schedules it to send at a future time. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + send_at: When to send the broadcast. If provided, the broadcast will be scheduled to send + at this time. Must be in ISO 8601 UTC format. If not provided, the broadcast + will be sent immediately. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return await self._put( + f"/v1/broadcasts/{broadcast_key}/send", + body=await async_maybe_transform({"send_at": send_at}, broadcast_send_params.BroadcastSendParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_send_params.BroadcastSendParams, + ), + ), + cast_to=BroadcastSendResponse, + ) + + async def upsert( + self, + broadcast_key: str, + *, + environment: str, + broadcast: BroadcastRequestParam, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastUpsertResponse: + """ + Updates a broadcast of a given key, or creates a new one if it does not yet + exist. + + Args: + environment: The environment slug. + + broadcast: A broadcast request for upserting a broadcast. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return await self._put( + f"/v1/broadcasts/{broadcast_key}", + body=await async_maybe_transform({"broadcast": broadcast}, broadcast_upsert_params.BroadcastUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + }, + broadcast_upsert_params.BroadcastUpsertParams, + ), + ), + cast_to=BroadcastUpsertResponse, + ) + + async def validate( + self, + broadcast_key: str, + *, + environment: str, + broadcast: BroadcastRequestParam, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BroadcastValidateResponse: + """ + Validates a broadcast payload without persisting it. + + Args: + environment: The environment slug. + + broadcast: A broadcast request for upserting a broadcast. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not broadcast_key: + raise ValueError(f"Expected a non-empty value for `broadcast_key` but received {broadcast_key!r}") + return await self._put( + f"/v1/broadcasts/{broadcast_key}/validate", + body=await async_maybe_transform( + {"broadcast": broadcast}, broadcast_validate_params.BroadcastValidateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + broadcast_validate_params.BroadcastValidateParams, + ), + ), + cast_to=BroadcastValidateResponse, + ) + + +class BroadcastsResourceWithRawResponse: + def __init__(self, broadcasts: BroadcastsResource) -> None: + self._broadcasts = broadcasts + + self.retrieve = to_raw_response_wrapper( + broadcasts.retrieve, + ) + self.list = to_raw_response_wrapper( + broadcasts.list, + ) + self.cancel = to_raw_response_wrapper( + broadcasts.cancel, + ) + self.send = to_raw_response_wrapper( + broadcasts.send, + ) + self.upsert = to_raw_response_wrapper( + broadcasts.upsert, + ) + self.validate = to_raw_response_wrapper( + broadcasts.validate, + ) + + +class AsyncBroadcastsResourceWithRawResponse: + def __init__(self, broadcasts: AsyncBroadcastsResource) -> None: + self._broadcasts = broadcasts + + self.retrieve = async_to_raw_response_wrapper( + broadcasts.retrieve, + ) + self.list = async_to_raw_response_wrapper( + broadcasts.list, + ) + self.cancel = async_to_raw_response_wrapper( + broadcasts.cancel, + ) + self.send = async_to_raw_response_wrapper( + broadcasts.send, + ) + self.upsert = async_to_raw_response_wrapper( + broadcasts.upsert, + ) + self.validate = async_to_raw_response_wrapper( + broadcasts.validate, + ) + + +class BroadcastsResourceWithStreamingResponse: + def __init__(self, broadcasts: BroadcastsResource) -> None: + self._broadcasts = broadcasts + + self.retrieve = to_streamed_response_wrapper( + broadcasts.retrieve, + ) + self.list = to_streamed_response_wrapper( + broadcasts.list, + ) + self.cancel = to_streamed_response_wrapper( + broadcasts.cancel, + ) + self.send = to_streamed_response_wrapper( + broadcasts.send, + ) + self.upsert = to_streamed_response_wrapper( + broadcasts.upsert, + ) + self.validate = to_streamed_response_wrapper( + broadcasts.validate, + ) + + +class AsyncBroadcastsResourceWithStreamingResponse: + def __init__(self, broadcasts: AsyncBroadcastsResource) -> None: + self._broadcasts = broadcasts + + self.retrieve = async_to_streamed_response_wrapper( + broadcasts.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + broadcasts.list, + ) + self.cancel = async_to_streamed_response_wrapper( + broadcasts.cancel, + ) + self.send = async_to_streamed_response_wrapper( + broadcasts.send, + ) + self.upsert = async_to_streamed_response_wrapper( + broadcasts.upsert, + ) + self.validate = async_to_streamed_response_wrapper( + broadcasts.validate, + ) diff --git a/src/knock_mapi/resources/channel_groups.py b/src/knock_mapi/resources/channel_groups.py index 74c3b7c7..686727da 100644 --- a/src/knock_mapi/resources/channel_groups.py +++ b/src/knock_mapi/resources/channel_groups.py @@ -4,9 +4,9 @@ import httpx -from ..types import channel_group_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from ..types import channel_group_list_params, channel_group_upsert_params +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -18,6 +18,7 @@ from ..pagination import SyncEntriesCursor, AsyncEntriesCursor from .._base_client import AsyncPaginator, make_request_options from ..types.channel_group import ChannelGroup +from ..types.channel_group_upsert_response import ChannelGroupUpsertResponse __all__ = ["ChannelGroupsResource", "AsyncChannelGroupsResource"] @@ -29,7 +30,7 @@ def with_raw_response(self) -> ChannelGroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return ChannelGroupsResourceWithRawResponse(self) @@ -38,22 +39,55 @@ def with_streaming_response(self) -> ChannelGroupsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return ChannelGroupsResourceWithStreamingResponse(self) + def retrieve( + self, + channel_group_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChannelGroup: + """ + Get a channel group by its key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + return self._get( + f"/v1/channel_groups/{channel_group_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelGroup, + ) + def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[ChannelGroup]: """Returns a paginated list of channel groups. @@ -95,6 +129,79 @@ def list( model=ChannelGroup, ) + def delete( + self, + channel_group_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Archives (soft deletes) a channel group by key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/v1/channel_groups/{channel_group_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def upsert( + self, + channel_group_key: str, + *, + channel_group: channel_group_upsert_params.ChannelGroup, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChannelGroupUpsertResponse: + """ + Creates or updates a channel group by key. + + Args: + channel_group: A request to create or update a channel group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + return self._put( + f"/v1/channel_groups/{channel_group_key}", + body=maybe_transform( + {"channel_group": channel_group}, channel_group_upsert_params.ChannelGroupUpsertParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelGroupUpsertResponse, + ) + class AsyncChannelGroupsResource(AsyncAPIResource): @cached_property @@ -103,7 +210,7 @@ def with_raw_response(self) -> AsyncChannelGroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncChannelGroupsResourceWithRawResponse(self) @@ -112,22 +219,55 @@ def with_streaming_response(self) -> AsyncChannelGroupsResourceWithStreamingResp """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncChannelGroupsResourceWithStreamingResponse(self) + async def retrieve( + self, + channel_group_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChannelGroup: + """ + Get a channel group by its key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + return await self._get( + f"/v1/channel_groups/{channel_group_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelGroup, + ) + def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ChannelGroup, AsyncEntriesCursor[ChannelGroup]]: """Returns a paginated list of channel groups. @@ -169,38 +309,147 @@ def list( model=ChannelGroup, ) + async def delete( + self, + channel_group_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Archives (soft deletes) a channel group by key. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/v1/channel_groups/{channel_group_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def upsert( + self, + channel_group_key: str, + *, + channel_group: channel_group_upsert_params.ChannelGroup, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ChannelGroupUpsertResponse: + """ + Creates or updates a channel group by key. + + Args: + channel_group: A request to create or update a channel group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_group_key: + raise ValueError(f"Expected a non-empty value for `channel_group_key` but received {channel_group_key!r}") + return await self._put( + f"/v1/channel_groups/{channel_group_key}", + body=await async_maybe_transform( + {"channel_group": channel_group}, channel_group_upsert_params.ChannelGroupUpsertParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ChannelGroupUpsertResponse, + ) + class ChannelGroupsResourceWithRawResponse: def __init__(self, channel_groups: ChannelGroupsResource) -> None: self._channel_groups = channel_groups + self.retrieve = to_raw_response_wrapper( + channel_groups.retrieve, + ) self.list = to_raw_response_wrapper( channel_groups.list, ) + self.delete = to_raw_response_wrapper( + channel_groups.delete, + ) + self.upsert = to_raw_response_wrapper( + channel_groups.upsert, + ) class AsyncChannelGroupsResourceWithRawResponse: def __init__(self, channel_groups: AsyncChannelGroupsResource) -> None: self._channel_groups = channel_groups + self.retrieve = async_to_raw_response_wrapper( + channel_groups.retrieve, + ) self.list = async_to_raw_response_wrapper( channel_groups.list, ) + self.delete = async_to_raw_response_wrapper( + channel_groups.delete, + ) + self.upsert = async_to_raw_response_wrapper( + channel_groups.upsert, + ) class ChannelGroupsResourceWithStreamingResponse: def __init__(self, channel_groups: ChannelGroupsResource) -> None: self._channel_groups = channel_groups + self.retrieve = to_streamed_response_wrapper( + channel_groups.retrieve, + ) self.list = to_streamed_response_wrapper( channel_groups.list, ) + self.delete = to_streamed_response_wrapper( + channel_groups.delete, + ) + self.upsert = to_streamed_response_wrapper( + channel_groups.upsert, + ) class AsyncChannelGroupsResourceWithStreamingResponse: def __init__(self, channel_groups: AsyncChannelGroupsResource) -> None: self._channel_groups = channel_groups + self.retrieve = async_to_streamed_response_wrapper( + channel_groups.retrieve, + ) self.list = async_to_streamed_response_wrapper( channel_groups.list, ) + self.delete = async_to_streamed_response_wrapper( + channel_groups.delete, + ) + self.upsert = async_to_streamed_response_wrapper( + channel_groups.upsert, + ) diff --git a/src/knock_mapi/resources/channels.py b/src/knock_mapi/resources/channels.py index 170f1ca1..36742667 100644 --- a/src/knock_mapi/resources/channels.py +++ b/src/knock_mapi/resources/channels.py @@ -2,10 +2,13 @@ from __future__ import annotations +from typing import List +from typing_extensions import Literal + import httpx from ..types import channel_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -29,7 +32,7 @@ def with_raw_response(self) -> ChannelsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return ChannelsResourceWithRawResponse(self) @@ -38,22 +41,60 @@ def with_streaming_response(self) -> ChannelsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return ChannelsResourceWithStreamingResponse(self) + def retrieve( + self, + channel_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Channel: + """Returns a channel with all environment-specific settings. + + Secret values in + provider settings are obfuscated unless they are Liquid templates (e.g., + `{{ vars.api_key }}`). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_key: + raise ValueError(f"Expected a non-empty value for `channel_key` but received {channel_key!r}") + return self._get( + f"/v1/channels/{channel_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Channel, + ) + def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + include: List[Literal["environment_settings"]] | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Channel]: """Returns a paginated list of channels. @@ -61,10 +102,15 @@ def list( entire account, not scoped to an environment. Args: + id: A channel id to filter the results by. + after: The cursor to fetch entries after. before: The cursor to fetch entries before. + include: Associated resources to include in the response. Accepts `environment_settings` + to include per-environment channel configuration. + limit: The number of entries to fetch per-page. extra_headers: Send extra headers @@ -85,8 +131,10 @@ def list( timeout=timeout, query=maybe_transform( { + "id": id, "after": after, "before": before, + "include": include, "limit": limit, }, channel_list_params.ChannelListParams, @@ -103,7 +151,7 @@ def with_raw_response(self) -> AsyncChannelsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncChannelsResourceWithRawResponse(self) @@ -112,22 +160,60 @@ def with_streaming_response(self) -> AsyncChannelsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncChannelsResourceWithStreamingResponse(self) + async def retrieve( + self, + channel_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Channel: + """Returns a channel with all environment-specific settings. + + Secret values in + provider settings are obfuscated unless they are Liquid templates (e.g., + `{{ vars.api_key }}`). + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not channel_key: + raise ValueError(f"Expected a non-empty value for `channel_key` but received {channel_key!r}") + return await self._get( + f"/v1/channels/{channel_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Channel, + ) + def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + id: str | Omit = omit, + after: str | Omit = omit, + before: str | Omit = omit, + include: List[Literal["environment_settings"]] | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Channel, AsyncEntriesCursor[Channel]]: """Returns a paginated list of channels. @@ -135,10 +221,15 @@ def list( entire account, not scoped to an environment. Args: + id: A channel id to filter the results by. + after: The cursor to fetch entries after. before: The cursor to fetch entries before. + include: Associated resources to include in the response. Accepts `environment_settings` + to include per-environment channel configuration. + limit: The number of entries to fetch per-page. extra_headers: Send extra headers @@ -159,8 +250,10 @@ def list( timeout=timeout, query=maybe_transform( { + "id": id, "after": after, "before": before, + "include": include, "limit": limit, }, channel_list_params.ChannelListParams, @@ -174,6 +267,9 @@ class ChannelsResourceWithRawResponse: def __init__(self, channels: ChannelsResource) -> None: self._channels = channels + self.retrieve = to_raw_response_wrapper( + channels.retrieve, + ) self.list = to_raw_response_wrapper( channels.list, ) @@ -183,6 +279,9 @@ class AsyncChannelsResourceWithRawResponse: def __init__(self, channels: AsyncChannelsResource) -> None: self._channels = channels + self.retrieve = async_to_raw_response_wrapper( + channels.retrieve, + ) self.list = async_to_raw_response_wrapper( channels.list, ) @@ -192,6 +291,9 @@ class ChannelsResourceWithStreamingResponse: def __init__(self, channels: ChannelsResource) -> None: self._channels = channels + self.retrieve = to_streamed_response_wrapper( + channels.retrieve, + ) self.list = to_streamed_response_wrapper( channels.list, ) @@ -201,6 +303,9 @@ class AsyncChannelsResourceWithStreamingResponse: def __init__(self, channels: AsyncChannelsResource) -> None: self._channels = channels + self.retrieve = async_to_streamed_response_wrapper( + channels.retrieve, + ) self.list = async_to_streamed_response_wrapper( channels.list, ) diff --git a/src/knock_mapi/resources/commits.py b/src/knock_mapi/resources/commits.py index 4e02cace..370ff009 100644 --- a/src/knock_mapi/resources/commits.py +++ b/src/knock_mapi/resources/commits.py @@ -2,10 +2,13 @@ from __future__ import annotations +from typing import List, Union +from typing_extensions import Literal + import httpx from ..types import commit_list_params, commit_commit_all_params, commit_promote_all_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -26,13 +29,15 @@ class CommitsResource(SyncAPIResource): + """Commits are versioned changes to resources.""" + @cached_property def with_raw_response(self) -> CommitsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return CommitsResourceWithRawResponse(self) @@ -41,7 +46,7 @@ def with_streaming_response(self) -> CommitsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return CommitsResourceWithStreamingResponse(self) @@ -54,7 +59,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Commit: """ Retrieve a single commit by its ID. @@ -82,16 +87,23 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - promoted: bool | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + limit: int | Omit = omit, + promoted: bool | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Commit]: """Returns a paginated list of commits in a given environment. @@ -105,11 +117,22 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + limit: The number of entries to fetch per-page. promoted: Whether to show commits in the given environment that have not been promoted to the subsequent environment (false) or commits which have been promoted (true). + resource_id: Filter commits by resource identifier. Must be used together with resource_type. + For most resources, this will be the resource key. In the case of translations, + this will be the locale code and namespace, separated by a `/`. For example, + `en/courses` or `en`. + + resource_type: Filter commits by resource type(s). Accepts a single type or array of types. Can + be combined with resource_id to filter for specific resources. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -131,8 +154,11 @@ def list( "environment": environment, "after": after, "before": before, + "branch": branch, "limit": limit, "promoted": promoted, + "resource_id": resource_id, + "resource_type": resource_type, }, commit_list_params.CommitListParams, ), @@ -144,13 +170,20 @@ def commit_all( self, *, environment: str, - commit_message: str | NotGiven = NOT_GIVEN, + branch: str | Omit = omit, + commit_message: str | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitCommitAllResponse: """ Commit all changes across all resources in the development environment. @@ -158,8 +191,17 @@ def commit_all( Args: environment: The environment slug. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit_message: An optional message to include in a commit. + resource_id: Filter changes to commit by resource identifier. Must be used together with + resource_type. + + resource_type: Filter changes to commit by resource type(s). Accepts a single type or array of + types. Can be combined with resource_id to filter for specific resources. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -178,7 +220,10 @@ def commit_all( query=maybe_transform( { "environment": environment, + "branch": branch, "commit_message": commit_message, + "resource_id": resource_id, + "resource_type": resource_type, }, commit_commit_all_params.CommitCommitAllParams, ), @@ -190,12 +235,19 @@ def promote_all( self, *, to_environment: str, + branch: str | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitPromoteAllResponse: """ Promote all changes across all resources to the target environment from its @@ -209,7 +261,16 @@ def promote_all( “production” (in that order), setting this param to “production” will promote all commits not currently in production from staging. - Note: This must be a non-development environment. + When this param is set to `"development"`, the `"branch"` param must be + provided. + + branch: The slug of the branch to promote all changes from. + + resource_id: Filter commits to promote by resource identifier. Must be used together with + resource_type. + + resource_type: Filter commits to promote by resource type(s). Accepts a single type or array of + types. Can be combined with resource_id to filter for specific resources. extra_headers: Send extra headers @@ -227,7 +288,13 @@ def promote_all( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"to_environment": to_environment}, commit_promote_all_params.CommitPromoteAllParams + { + "to_environment": to_environment, + "branch": branch, + "resource_id": resource_id, + "resource_type": resource_type, + }, + commit_promote_all_params.CommitPromoteAllParams, ), ), cast_to=CommitPromoteAllResponse, @@ -242,7 +309,7 @@ def promote_one( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitPromoteOneResponse: """ Promotes one change to the subsequent environment. @@ -268,13 +335,15 @@ def promote_one( class AsyncCommitsResource(AsyncAPIResource): + """Commits are versioned changes to resources.""" + @cached_property def with_raw_response(self) -> AsyncCommitsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncCommitsResourceWithRawResponse(self) @@ -283,7 +352,7 @@ def with_streaming_response(self) -> AsyncCommitsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncCommitsResourceWithStreamingResponse(self) @@ -296,7 +365,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Commit: """ Retrieve a single commit by its ID. @@ -324,16 +393,23 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - promoted: bool | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + limit: int | Omit = omit, + promoted: bool | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Commit, AsyncEntriesCursor[Commit]]: """Returns a paginated list of commits in a given environment. @@ -347,11 +423,22 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + limit: The number of entries to fetch per-page. promoted: Whether to show commits in the given environment that have not been promoted to the subsequent environment (false) or commits which have been promoted (true). + resource_id: Filter commits by resource identifier. Must be used together with resource_type. + For most resources, this will be the resource key. In the case of translations, + this will be the locale code and namespace, separated by a `/`. For example, + `en/courses` or `en`. + + resource_type: Filter commits by resource type(s). Accepts a single type or array of types. Can + be combined with resource_id to filter for specific resources. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -373,8 +460,11 @@ def list( "environment": environment, "after": after, "before": before, + "branch": branch, "limit": limit, "promoted": promoted, + "resource_id": resource_id, + "resource_type": resource_type, }, commit_list_params.CommitListParams, ), @@ -386,13 +476,20 @@ async def commit_all( self, *, environment: str, - commit_message: str | NotGiven = NOT_GIVEN, + branch: str | Omit = omit, + commit_message: str | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitCommitAllResponse: """ Commit all changes across all resources in the development environment. @@ -400,8 +497,17 @@ async def commit_all( Args: environment: The environment slug. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit_message: An optional message to include in a commit. + resource_id: Filter changes to commit by resource identifier. Must be used together with + resource_type. + + resource_type: Filter changes to commit by resource type(s). Accepts a single type or array of + types. Can be combined with resource_id to filter for specific resources. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -420,7 +526,10 @@ async def commit_all( query=await async_maybe_transform( { "environment": environment, + "branch": branch, "commit_message": commit_message, + "resource_id": resource_id, + "resource_type": resource_type, }, commit_commit_all_params.CommitCommitAllParams, ), @@ -432,12 +541,19 @@ async def promote_all( self, *, to_environment: str, + branch: str | Omit = omit, + resource_id: str | Omit = omit, + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitPromoteAllResponse: """ Promote all changes across all resources to the target environment from its @@ -451,7 +567,16 @@ async def promote_all( “production” (in that order), setting this param to “production” will promote all commits not currently in production from staging. - Note: This must be a non-development environment. + When this param is set to `"development"`, the `"branch"` param must be + provided. + + branch: The slug of the branch to promote all changes from. + + resource_id: Filter commits to promote by resource identifier. Must be used together with + resource_type. + + resource_type: Filter commits to promote by resource type(s). Accepts a single type or array of + types. Can be combined with resource_id to filter for specific resources. extra_headers: Send extra headers @@ -469,7 +594,13 @@ async def promote_all( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"to_environment": to_environment}, commit_promote_all_params.CommitPromoteAllParams + { + "to_environment": to_environment, + "branch": branch, + "resource_id": resource_id, + "resource_type": resource_type, + }, + commit_promote_all_params.CommitPromoteAllParams, ), ), cast_to=CommitPromoteAllResponse, @@ -484,7 +615,7 @@ async def promote_one( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> CommitPromoteOneResponse: """ Promotes one change to the subsequent environment. diff --git a/src/knock_mapi/resources/email_layouts.py b/src/knock_mapi/resources/email_layouts.py index 213de0fe..58a92dbf 100644 --- a/src/knock_mapi/resources/email_layouts.py +++ b/src/knock_mapi/resources/email_layouts.py @@ -10,7 +10,7 @@ email_layout_retrieve_params, email_layout_validate_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -30,13 +30,15 @@ class EmailLayoutsResource(SyncAPIResource): + """Email layouts wrap your email templates and provide a consistent look and feel.""" + @cached_property def with_raw_response(self) -> EmailLayoutsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return EmailLayoutsResourceWithRawResponse(self) @@ -45,7 +47,7 @@ def with_streaming_response(self) -> EmailLayoutsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return EmailLayoutsResourceWithStreamingResponse(self) @@ -54,14 +56,15 @@ def retrieve( email_layout_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayout: """ Retrieve an email layout by its key, in a given environment. @@ -71,6 +74,9 @@ def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -95,6 +101,7 @@ def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, email_layout_retrieve_params.EmailLayoutRetrieveParams, @@ -107,17 +114,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[EmailLayout]: """ Returns a paginated list of email layouts available in a given environment. @@ -131,6 +139,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -158,6 +169,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -173,15 +185,16 @@ def upsert( *, environment: str, email_layout: email_layout_upsert_params.EmailLayout, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayoutUpsertResponse: """ Updates an email layout, or creates a new one if it does not yet exist. @@ -195,6 +208,9 @@ def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -221,6 +237,7 @@ def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -236,12 +253,13 @@ def validate( *, environment: str, email_layout: email_layout_validate_params.EmailLayout, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayoutValidateResponse: """ Validates an email layout payload without persisting it. @@ -253,6 +271,9 @@ def validate( email_layout: A request to update or create an email layout. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -274,7 +295,11 @@ def validate( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"environment": environment}, email_layout_validate_params.EmailLayoutValidateParams + { + "environment": environment, + "branch": branch, + }, + email_layout_validate_params.EmailLayoutValidateParams, ), ), cast_to=EmailLayoutValidateResponse, @@ -282,13 +307,15 @@ def validate( class AsyncEmailLayoutsResource(AsyncAPIResource): + """Email layouts wrap your email templates and provide a consistent look and feel.""" + @cached_property def with_raw_response(self) -> AsyncEmailLayoutsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncEmailLayoutsResourceWithRawResponse(self) @@ -297,7 +324,7 @@ def with_streaming_response(self) -> AsyncEmailLayoutsResourceWithStreamingRespo """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncEmailLayoutsResourceWithStreamingResponse(self) @@ -306,14 +333,15 @@ async def retrieve( email_layout_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayout: """ Retrieve an email layout by its key, in a given environment. @@ -323,6 +351,9 @@ async def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -347,6 +378,7 @@ async def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, email_layout_retrieve_params.EmailLayoutRetrieveParams, @@ -359,17 +391,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[EmailLayout, AsyncEntriesCursor[EmailLayout]]: """ Returns a paginated list of email layouts available in a given environment. @@ -383,6 +416,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -410,6 +446,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -425,15 +462,16 @@ async def upsert( *, environment: str, email_layout: email_layout_upsert_params.EmailLayout, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayoutUpsertResponse: """ Updates an email layout, or creates a new one if it does not yet exist. @@ -447,6 +485,9 @@ async def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -475,6 +516,7 @@ async def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -490,12 +532,13 @@ async def validate( *, environment: str, email_layout: email_layout_validate_params.EmailLayout, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> EmailLayoutValidateResponse: """ Validates an email layout payload without persisting it. @@ -507,6 +550,9 @@ async def validate( email_layout: A request to update or create an email layout. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -528,7 +574,11 @@ async def validate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, email_layout_validate_params.EmailLayoutValidateParams + { + "environment": environment, + "branch": branch, + }, + email_layout_validate_params.EmailLayoutValidateParams, ), ), cast_to=EmailLayoutValidateResponse, diff --git a/src/knock_mapi/resources/environments.py b/src/knock_mapi/resources/environments.py index a43c907c..cc031dc8 100644 --- a/src/knock_mapi/resources/environments.py +++ b/src/knock_mapi/resources/environments.py @@ -5,7 +5,7 @@ import httpx from ..types import environment_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -23,13 +23,17 @@ class EnvironmentsResource(SyncAPIResource): + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + @cached_property def with_raw_response(self) -> EnvironmentsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return EnvironmentsResourceWithRawResponse(self) @@ -38,7 +42,7 @@ def with_streaming_response(self) -> EnvironmentsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return EnvironmentsResourceWithStreamingResponse(self) @@ -51,7 +55,7 @@ def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Environment: """ Returns a single environment by the `environment_slug`. @@ -78,15 +82,15 @@ def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Environment]: """Returns a paginated list of environments. @@ -130,13 +134,17 @@ def list( class AsyncEnvironmentsResource(AsyncAPIResource): + """ + Environments are isolated instances of your account that map to your infrastructure. + """ + @cached_property def with_raw_response(self) -> AsyncEnvironmentsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncEnvironmentsResourceWithRawResponse(self) @@ -145,7 +153,7 @@ def with_streaming_response(self) -> AsyncEnvironmentsResourceWithStreamingRespo """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncEnvironmentsResourceWithStreamingResponse(self) @@ -158,7 +166,7 @@ async def retrieve( extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Environment: """ Returns a single environment by the `environment_slug`. @@ -185,15 +193,15 @@ async def retrieve( def list( self, *, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Environment, AsyncEntriesCursor[Environment]]: """Returns a paginated list of environments. diff --git a/src/knock_mapi/resources/guides.py b/src/knock_mapi/resources/guides.py new file mode 100644 index 00000000..72a046b1 --- /dev/null +++ b/src/knock_mapi/resources/guides.py @@ -0,0 +1,1016 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import overload + +import httpx + +from ..types import ( + guide_list_params, + guide_upsert_params, + guide_activate_params, + guide_retrieve_params, + guide_validate_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import required_args, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncEntriesCursor, AsyncEntriesCursor +from ..types.guide import Guide +from .._base_client import AsyncPaginator, make_request_options +from ..types.guide_upsert_response import GuideUpsertResponse +from ..types.guide_archive_response import GuideArchiveResponse +from ..types.guide_activate_response import GuideActivateResponse +from ..types.guide_validate_response import GuideValidateResponse + +__all__ = ["GuidesResource", "AsyncGuidesResource"] + + +class GuidesResource(SyncAPIResource): + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + + @cached_property + def with_raw_response(self) -> GuidesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return GuidesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> GuidesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return GuidesResourceWithStreamingResponse(self) + + def retrieve( + self, + guide_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Guide: + """ + Get a guide by its key. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return self._get( + f"/v1/guides/{guide_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + guide_retrieve_params.GuideRetrieveParams, + ), + ), + cast_to=Guide, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncEntriesCursor[Guide]: + """ + Returns a paginated list of guides available in a given environment. + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/guides", + page=SyncEntriesCursor[Guide], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + guide_list_params.GuideListParams, + ), + ), + model=Guide, + ) + + @overload + def activate( + self, + guide_key: str, + *, + environment: str, + status: bool, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + """Activates (or deactivates) a guide in a given environment. + + You can either set + the active status immediately or schedule it. + + Note: This immediately enables or disables a guide in a given environment + without needing to go through environment promotion. + + Args: + environment: The environment slug. + + status: Whether to activate or deactivate the guide. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + def activate( + self, + guide_key: str, + *, + environment: str, + branch: str | Omit = omit, + from_: Union[str, datetime] | Omit = omit, + until: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + """Activates (or deactivates) a guide in a given environment. + + You can either set + the active status immediately or schedule it. + + Note: This immediately enables or disables a guide in a given environment + without needing to go through environment promotion. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + from_: When to activate the guide. If provided, the guide will be scheduled to activate + at this time. Must be in ISO 8601 UTC format. + + until: When to deactivate the guide. If provided, the guide will be scheduled to + deactivate at this time. Must be in ISO 8601 UTC format. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["environment", "status"], ["environment"]) + def activate( + self, + guide_key: str, + *, + environment: str, + status: bool | Omit = omit, + branch: str | Omit = omit, + from_: Union[str, datetime] | Omit = omit, + until: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return self._put( + f"/v1/guides/{guide_key}/activate", + body=maybe_transform( + { + "status": status, + "from_": from_, + "until": until, + }, + guide_activate_params.GuideActivateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + guide_activate_params.GuideActivateParams, + ), + ), + cast_to=GuideActivateResponse, + ) + + def archive( + self, + guide_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideArchiveResponse: + """ + Archives a given guide across all environments. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return self._delete( + f"/v1/guides/{guide_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GuideArchiveResponse, + ) + + def upsert( + self, + guide_key: str, + *, + environment: str, + guide: guide_upsert_params.Guide, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideUpsertResponse: + """ + Updates a guide of a given key, or creates a new one if it does not yet exist. + + Note: this endpoint only operates on guides in the "development" environment. + + Args: + environment: The environment slug. + + guide: A request to create or update a guide. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + commit: Whether to commit the resource at the same time as modifying it. + + commit_message: The message to commit the resource with, only used if `commit` is `true`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return self._put( + f"/v1/guides/{guide_key}", + body=maybe_transform({"guide": guide}, guide_upsert_params.GuideUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "commit": commit, + "commit_message": commit_message, + }, + guide_upsert_params.GuideUpsertParams, + ), + ), + cast_to=GuideUpsertResponse, + ) + + def validate( + self, + guide_key: str, + *, + environment: str, + guide: guide_validate_params.Guide, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideValidateResponse: + """ + Validates a guide payload without persisting it. + + Note: Validating a guide is only done in the development environment context. + + Args: + environment: The environment slug. + + guide: A request to create or update a guide. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return self._put( + f"/v1/guides/{guide_key}/validate", + body=maybe_transform({"guide": guide}, guide_validate_params.GuideValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + guide_validate_params.GuideValidateParams, + ), + ), + cast_to=GuideValidateResponse, + ) + + +class AsyncGuidesResource(AsyncAPIResource): + """ + Guides let you define in-app guides that can be displayed to users based on priority and other conditions. + """ + + @cached_property + def with_raw_response(self) -> AsyncGuidesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AsyncGuidesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncGuidesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AsyncGuidesResourceWithStreamingResponse(self) + + async def retrieve( + self, + guide_key: str, + *, + environment: str, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Guide: + """ + Get a guide by its key. + + Args: + environment: The environment slug. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return await self._get( + f"/v1/guides/{guide_key}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + }, + guide_retrieve_params.GuideRetrieveParams, + ), + ), + cast_to=Guide, + ) + + def list( + self, + *, + environment: str, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Guide, AsyncEntriesCursor[Guide]]: + """ + Returns a paginated list of guides available in a given environment. + + Args: + environment: The environment slug. + + after: The cursor to fetch entries after. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + before: The cursor to fetch entries before. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be + returned. When false, both committed and uncommitted changes will be returned. + + limit: The number of entries to fetch per-page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/guides", + page=AsyncEntriesCursor[Guide], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "environment": environment, + "after": after, + "annotate": annotate, + "before": before, + "branch": branch, + "hide_uncommitted_changes": hide_uncommitted_changes, + "limit": limit, + }, + guide_list_params.GuideListParams, + ), + ), + model=Guide, + ) + + @overload + async def activate( + self, + guide_key: str, + *, + environment: str, + status: bool, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + """Activates (or deactivates) a guide in a given environment. + + You can either set + the active status immediately or schedule it. + + Note: This immediately enables or disables a guide in a given environment + without needing to go through environment promotion. + + Args: + environment: The environment slug. + + status: Whether to activate or deactivate the guide. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @overload + async def activate( + self, + guide_key: str, + *, + environment: str, + branch: str | Omit = omit, + from_: Union[str, datetime] | Omit = omit, + until: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + """Activates (or deactivates) a guide in a given environment. + + You can either set + the active status immediately or schedule it. + + Note: This immediately enables or disables a guide in a given environment + without needing to go through environment promotion. + + Args: + environment: The environment slug. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + from_: When to activate the guide. If provided, the guide will be scheduled to activate + at this time. Must be in ISO 8601 UTC format. + + until: When to deactivate the guide. If provided, the guide will be scheduled to + deactivate at this time. Must be in ISO 8601 UTC format. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + ... + + @required_args(["environment", "status"], ["environment"]) + async def activate( + self, + guide_key: str, + *, + environment: str, + status: bool | Omit = omit, + branch: str | Omit = omit, + from_: Union[str, datetime] | Omit = omit, + until: Union[str, datetime] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideActivateResponse: + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return await self._put( + f"/v1/guides/{guide_key}/activate", + body=await async_maybe_transform( + { + "status": status, + "from_": from_, + "until": until, + }, + guide_activate_params.GuideActivateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + guide_activate_params.GuideActivateParams, + ), + ), + cast_to=GuideActivateResponse, + ) + + async def archive( + self, + guide_key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideArchiveResponse: + """ + Archives a given guide across all environments. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return await self._delete( + f"/v1/guides/{guide_key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GuideArchiveResponse, + ) + + async def upsert( + self, + guide_key: str, + *, + environment: str, + guide: guide_upsert_params.Guide, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideUpsertResponse: + """ + Updates a guide of a given key, or creates a new one if it does not yet exist. + + Note: this endpoint only operates on guides in the "development" environment. + + Args: + environment: The environment slug. + + guide: A request to create or update a guide. + + annotate: Whether to annotate the resource. Only used in the Knock CLI. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + commit: Whether to commit the resource at the same time as modifying it. + + commit_message: The message to commit the resource with, only used if `commit` is `true`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return await self._put( + f"/v1/guides/{guide_key}", + body=await async_maybe_transform({"guide": guide}, guide_upsert_params.GuideUpsertParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "annotate": annotate, + "branch": branch, + "commit": commit, + "commit_message": commit_message, + }, + guide_upsert_params.GuideUpsertParams, + ), + ), + cast_to=GuideUpsertResponse, + ) + + async def validate( + self, + guide_key: str, + *, + environment: str, + guide: guide_validate_params.Guide, + branch: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GuideValidateResponse: + """ + Validates a guide payload without persisting it. + + Note: Validating a guide is only done in the development environment context. + + Args: + environment: The environment slug. + + guide: A request to create or update a guide. + + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not guide_key: + raise ValueError(f"Expected a non-empty value for `guide_key` but received {guide_key!r}") + return await self._put( + f"/v1/guides/{guide_key}/validate", + body=await async_maybe_transform({"guide": guide}, guide_validate_params.GuideValidateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + guide_validate_params.GuideValidateParams, + ), + ), + cast_to=GuideValidateResponse, + ) + + +class GuidesResourceWithRawResponse: + def __init__(self, guides: GuidesResource) -> None: + self._guides = guides + + self.retrieve = to_raw_response_wrapper( + guides.retrieve, + ) + self.list = to_raw_response_wrapper( + guides.list, + ) + self.activate = to_raw_response_wrapper( + guides.activate, + ) + self.archive = to_raw_response_wrapper( + guides.archive, + ) + self.upsert = to_raw_response_wrapper( + guides.upsert, + ) + self.validate = to_raw_response_wrapper( + guides.validate, + ) + + +class AsyncGuidesResourceWithRawResponse: + def __init__(self, guides: AsyncGuidesResource) -> None: + self._guides = guides + + self.retrieve = async_to_raw_response_wrapper( + guides.retrieve, + ) + self.list = async_to_raw_response_wrapper( + guides.list, + ) + self.activate = async_to_raw_response_wrapper( + guides.activate, + ) + self.archive = async_to_raw_response_wrapper( + guides.archive, + ) + self.upsert = async_to_raw_response_wrapper( + guides.upsert, + ) + self.validate = async_to_raw_response_wrapper( + guides.validate, + ) + + +class GuidesResourceWithStreamingResponse: + def __init__(self, guides: GuidesResource) -> None: + self._guides = guides + + self.retrieve = to_streamed_response_wrapper( + guides.retrieve, + ) + self.list = to_streamed_response_wrapper( + guides.list, + ) + self.activate = to_streamed_response_wrapper( + guides.activate, + ) + self.archive = to_streamed_response_wrapper( + guides.archive, + ) + self.upsert = to_streamed_response_wrapper( + guides.upsert, + ) + self.validate = to_streamed_response_wrapper( + guides.validate, + ) + + +class AsyncGuidesResourceWithStreamingResponse: + def __init__(self, guides: AsyncGuidesResource) -> None: + self._guides = guides + + self.retrieve = async_to_streamed_response_wrapper( + guides.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + guides.list, + ) + self.activate = async_to_streamed_response_wrapper( + guides.activate, + ) + self.archive = async_to_streamed_response_wrapper( + guides.archive, + ) + self.upsert = async_to_streamed_response_wrapper( + guides.upsert, + ) + self.validate = async_to_streamed_response_wrapper( + guides.validate, + ) diff --git a/src/knock_mapi/resources/members.py b/src/knock_mapi/resources/members.py new file mode 100644 index 00000000..b3ff0fbd --- /dev/null +++ b/src/knock_mapi/resources/members.py @@ -0,0 +1,382 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import member_list_params +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncEntriesCursor, AsyncEntriesCursor +from .._base_client import AsyncPaginator, make_request_options +from ..types.member import Member + +__all__ = ["MembersResource", "AsyncMembersResource"] + + +class MembersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> MembersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return MembersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MembersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return MembersResourceWithStreamingResponse(self) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Member: + """ + Returns a single member by their ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/v1/members/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Member, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + email: str | Omit = omit, + limit: int | Omit = omit, + role: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncEntriesCursor[Member]: + """Returns a paginated list of members for the current account. + + Optionally filter + by role. + + Args: + after: The cursor to fetch entries after. + + before: The cursor to fetch entries before. + + email: Filter members by email address (exact match). + + limit: The number of entries to fetch per-page. + + role: Filter members by role. One of: owner, admin, member, production_only_member, + billing, support. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/members", + page=SyncEntriesCursor[Member], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "email": email, + "limit": limit, + "role": role, + }, + member_list_params.MemberListParams, + ), + ), + model=Member, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Removes a member from the account. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/v1/members/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncMembersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncMembersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers + """ + return AsyncMembersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMembersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response + """ + return AsyncMembersResourceWithStreamingResponse(self) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Member: + """ + Returns a single member by their ID. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/v1/members/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Member, + ) + + def list( + self, + *, + after: str | Omit = omit, + before: str | Omit = omit, + email: str | Omit = omit, + limit: int | Omit = omit, + role: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[Member, AsyncEntriesCursor[Member]]: + """Returns a paginated list of members for the current account. + + Optionally filter + by role. + + Args: + after: The cursor to fetch entries after. + + before: The cursor to fetch entries before. + + email: Filter members by email address (exact match). + + limit: The number of entries to fetch per-page. + + role: Filter members by role. One of: owner, admin, member, production_only_member, + billing, support. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/members", + page=AsyncEntriesCursor[Member], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "email": email, + "limit": limit, + "role": role, + }, + member_list_params.MemberListParams, + ), + ), + model=Member, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Removes a member from the account. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/v1/members/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class MembersResourceWithRawResponse: + def __init__(self, members: MembersResource) -> None: + self._members = members + + self.retrieve = to_raw_response_wrapper( + members.retrieve, + ) + self.list = to_raw_response_wrapper( + members.list, + ) + self.delete = to_raw_response_wrapper( + members.delete, + ) + + +class AsyncMembersResourceWithRawResponse: + def __init__(self, members: AsyncMembersResource) -> None: + self._members = members + + self.retrieve = async_to_raw_response_wrapper( + members.retrieve, + ) + self.list = async_to_raw_response_wrapper( + members.list, + ) + self.delete = async_to_raw_response_wrapper( + members.delete, + ) + + +class MembersResourceWithStreamingResponse: + def __init__(self, members: MembersResource) -> None: + self._members = members + + self.retrieve = to_streamed_response_wrapper( + members.retrieve, + ) + self.list = to_streamed_response_wrapper( + members.list, + ) + self.delete = to_streamed_response_wrapper( + members.delete, + ) + + +class AsyncMembersResourceWithStreamingResponse: + def __init__(self, members: AsyncMembersResource) -> None: + self._members = members + + self.retrieve = async_to_streamed_response_wrapper( + members.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + members.list, + ) + self.delete = async_to_streamed_response_wrapper( + members.delete, + ) diff --git a/src/knock_mapi/resources/message_types.py b/src/knock_mapi/resources/message_types.py index 375f9746..bb22c523 100644 --- a/src/knock_mapi/resources/message_types.py +++ b/src/knock_mapi/resources/message_types.py @@ -10,7 +10,7 @@ message_type_retrieve_params, message_type_validate_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -30,13 +30,17 @@ class MessageTypesResource(SyncAPIResource): + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + @cached_property def with_raw_response(self) -> MessageTypesResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return MessageTypesResourceWithRawResponse(self) @@ -45,7 +49,7 @@ def with_streaming_response(self) -> MessageTypesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return MessageTypesResourceWithStreamingResponse(self) @@ -54,14 +58,15 @@ def retrieve( message_type_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageType: """ Retrieve a message type by its key, in a given environment. @@ -71,6 +76,9 @@ def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -95,6 +103,7 @@ def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, message_type_retrieve_params.MessageTypeRetrieveParams, @@ -107,17 +116,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[MessageType]: """ Returns a paginated list of message types available in a given environment. @@ -131,6 +141,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -158,6 +171,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -173,15 +187,16 @@ def upsert( *, environment: str, message_type: message_type_upsert_params.MessageType, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageTypeUpsertResponse: """ Updates a message type, or creates a new one if it does not yet exist. @@ -195,6 +210,9 @@ def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -221,6 +239,7 @@ def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -236,12 +255,13 @@ def validate( *, environment: str, message_type: message_type_validate_params.MessageType, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageTypeValidateResponse: """ Validates a message type payload without persisting it. @@ -254,6 +274,9 @@ def validate( message_type: A request to create a message type. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -275,7 +298,11 @@ def validate( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"environment": environment}, message_type_validate_params.MessageTypeValidateParams + { + "environment": environment, + "branch": branch, + }, + message_type_validate_params.MessageTypeValidateParams, ), ), cast_to=MessageTypeValidateResponse, @@ -283,13 +310,17 @@ def validate( class AsyncMessageTypesResource(AsyncAPIResource): + """ + A message type allows you to specify an in-app schema that defines the fields available for your in-app notifications. + """ + @cached_property def with_raw_response(self) -> AsyncMessageTypesResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncMessageTypesResourceWithRawResponse(self) @@ -298,7 +329,7 @@ def with_streaming_response(self) -> AsyncMessageTypesResourceWithStreamingRespo """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncMessageTypesResourceWithStreamingResponse(self) @@ -307,14 +338,15 @@ async def retrieve( message_type_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageType: """ Retrieve a message type by its key, in a given environment. @@ -324,6 +356,9 @@ async def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -348,6 +383,7 @@ async def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, message_type_retrieve_params.MessageTypeRetrieveParams, @@ -360,17 +396,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[MessageType, AsyncEntriesCursor[MessageType]]: """ Returns a paginated list of message types available in a given environment. @@ -384,6 +421,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -411,6 +451,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -426,15 +467,16 @@ async def upsert( *, environment: str, message_type: message_type_upsert_params.MessageType, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageTypeUpsertResponse: """ Updates a message type, or creates a new one if it does not yet exist. @@ -448,6 +490,9 @@ async def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -476,6 +521,7 @@ async def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -491,12 +537,13 @@ async def validate( *, environment: str, message_type: message_type_validate_params.MessageType, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> MessageTypeValidateResponse: """ Validates a message type payload without persisting it. @@ -509,6 +556,9 @@ async def validate( message_type: A request to create a message type. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -530,7 +580,11 @@ async def validate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, message_type_validate_params.MessageTypeValidateParams + { + "environment": environment, + "branch": branch, + }, + message_type_validate_params.MessageTypeValidateParams, ), ), cast_to=MessageTypeValidateResponse, diff --git a/src/knock_mapi/resources/partials.py b/src/knock_mapi/resources/partials.py index ee61bf3e..615a0dad 100644 --- a/src/knock_mapi/resources/partials.py +++ b/src/knock_mapi/resources/partials.py @@ -5,7 +5,7 @@ import httpx from ..types import partial_list_params, partial_upsert_params, partial_retrieve_params, partial_validate_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -25,13 +25,15 @@ class PartialsResource(SyncAPIResource): + """Partials allow you to reuse content across templates.""" + @cached_property def with_raw_response(self) -> PartialsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return PartialsResourceWithRawResponse(self) @@ -40,7 +42,7 @@ def with_streaming_response(self) -> PartialsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return PartialsResourceWithStreamingResponse(self) @@ -49,14 +51,15 @@ def retrieve( partial_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Partial: """ Get a partial by its key. @@ -66,6 +69,9 @@ def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -90,6 +96,7 @@ def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, partial_retrieve_params.PartialRetrieveParams, @@ -102,17 +109,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Partial]: """ List all partials for a given environment. @@ -126,6 +134,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -153,6 +164,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -168,15 +180,16 @@ def upsert( *, environment: str, partial: partial_upsert_params.Partial, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PartialUpsertResponse: """ Updates a partial of a given key, or creates a new one if it does not yet exist. @@ -190,6 +203,9 @@ def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -216,6 +232,7 @@ def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -231,12 +248,13 @@ def validate( *, environment: str, partial: partial_validate_params.Partial, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PartialValidateResponse: """ Validates a partial payload without persisting it. @@ -248,6 +266,9 @@ def validate( partial: A partial object with attributes to update or create a partial. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -266,20 +287,28 @@ def validate( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"environment": environment}, partial_validate_params.PartialValidateParams), + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + partial_validate_params.PartialValidateParams, + ), ), cast_to=PartialValidateResponse, ) class AsyncPartialsResource(AsyncAPIResource): + """Partials allow you to reuse content across templates.""" + @cached_property def with_raw_response(self) -> AsyncPartialsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncPartialsResourceWithRawResponse(self) @@ -288,7 +317,7 @@ def with_streaming_response(self) -> AsyncPartialsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncPartialsResourceWithStreamingResponse(self) @@ -297,14 +326,15 @@ async def retrieve( partial_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Partial: """ Get a partial by its key. @@ -314,6 +344,9 @@ async def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -338,6 +371,7 @@ async def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, partial_retrieve_params.PartialRetrieveParams, @@ -350,17 +384,18 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Partial, AsyncEntriesCursor[Partial]]: """ List all partials for a given environment. @@ -374,6 +409,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -401,6 +439,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -416,15 +455,16 @@ async def upsert( *, environment: str, partial: partial_upsert_params.Partial, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PartialUpsertResponse: """ Updates a partial of a given key, or creates a new one if it does not yet exist. @@ -438,6 +478,9 @@ async def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -464,6 +507,7 @@ async def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -479,12 +523,13 @@ async def validate( *, environment: str, partial: partial_validate_params.Partial, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> PartialValidateResponse: """ Validates a partial payload without persisting it. @@ -496,6 +541,9 @@ async def validate( partial: A partial object with attributes to update or create a partial. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -515,7 +563,11 @@ async def validate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, partial_validate_params.PartialValidateParams + { + "environment": environment, + "branch": branch, + }, + partial_validate_params.PartialValidateParams, ), ), cast_to=PartialValidateResponse, diff --git a/src/knock_mapi/resources/translations.py b/src/knock_mapi/resources/translations.py index 678a0ee4..a2887f3f 100644 --- a/src/knock_mapi/resources/translations.py +++ b/src/knock_mapi/resources/translations.py @@ -12,7 +12,7 @@ translation_retrieve_params, translation_validate_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -33,13 +33,15 @@ class TranslationsResource(SyncAPIResource): + """Translations are per-locale string files that can be used in your templates.""" + @cached_property def with_raw_response(self) -> TranslationsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return TranslationsResourceWithRawResponse(self) @@ -48,7 +50,7 @@ def with_streaming_response(self) -> TranslationsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return TranslationsResourceWithStreamingResponse(self) @@ -57,16 +59,17 @@ def retrieve( locale_code: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - namespace: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + namespace: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationRetrieveResponse: """ Retrieve a translation by its locale and namespace, in a given environment. @@ -76,6 +79,9 @@ def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + format: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. @@ -105,6 +111,7 @@ def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "format": format, "hide_uncommitted_changes": hide_uncommitted_changes, "namespace": namespace, @@ -119,20 +126,21 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - locale_code: str | NotGiven = NOT_GIVEN, - namespace: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + locale_code: str | Omit = omit, + namespace: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Translation]: """Returns a paginated list of translations available in a given environment. @@ -148,6 +156,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + format: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. @@ -182,6 +193,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "format": format, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, @@ -201,16 +213,17 @@ def upsert( environment: str, namespace: str, translation: translation_upsert_params.Translation, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationUpsertResponse: """ Updates a translation of a given locale code + namespace, or creates a new one @@ -229,6 +242,9 @@ def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -259,6 +275,7 @@ def upsert( "environment": environment, "namespace": namespace, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, "format": format, @@ -275,12 +292,13 @@ def validate( *, environment: str, translation: translation_validate_params.Translation, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationValidateResponse: """ Validates a translation payload without persisting it. @@ -294,6 +312,9 @@ def validate( translation: A translation object with a content attribute used to update or create a translation. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -313,7 +334,11 @@ def validate( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"environment": environment}, translation_validate_params.TranslationValidateParams + { + "environment": environment, + "branch": branch, + }, + translation_validate_params.TranslationValidateParams, ), ), cast_to=TranslationValidateResponse, @@ -321,13 +346,15 @@ def validate( class AsyncTranslationsResource(AsyncAPIResource): + """Translations are per-locale string files that can be used in your templates.""" + @cached_property def with_raw_response(self) -> AsyncTranslationsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncTranslationsResourceWithRawResponse(self) @@ -336,7 +363,7 @@ def with_streaming_response(self) -> AsyncTranslationsResourceWithStreamingRespo """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncTranslationsResourceWithStreamingResponse(self) @@ -345,16 +372,17 @@ async def retrieve( locale_code: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - namespace: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + namespace: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationRetrieveResponse: """ Retrieve a translation by its locale and namespace, in a given environment. @@ -364,6 +392,9 @@ async def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + format: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. @@ -393,6 +424,7 @@ async def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "format": format, "hide_uncommitted_changes": hide_uncommitted_changes, "namespace": namespace, @@ -407,20 +439,21 @@ def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - locale_code: str | NotGiven = NOT_GIVEN, - namespace: str | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, + locale_code: str | Omit = omit, + namespace: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Translation, AsyncEntriesCursor[Translation]]: """Returns a paginated list of translations available in a given environment. @@ -436,6 +469,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + format: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. @@ -470,6 +506,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "format": format, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, @@ -489,16 +526,17 @@ async def upsert( environment: str, namespace: str, translation: translation_upsert_params.Translation, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, - format: Literal["json", "po"] | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, + format: Literal["json", "po"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationUpsertResponse: """ Updates a translation of a given locale code + namespace, or creates a new one @@ -517,6 +555,9 @@ async def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -549,6 +590,7 @@ async def upsert( "environment": environment, "namespace": namespace, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, "format": format, @@ -565,12 +607,13 @@ async def validate( *, environment: str, translation: translation_validate_params.Translation, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TranslationValidateResponse: """ Validates a translation payload without persisting it. @@ -584,6 +627,9 @@ async def validate( translation: A translation object with a content attribute used to update or create a translation. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -605,7 +651,11 @@ async def validate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, translation_validate_params.TranslationValidateParams + { + "environment": environment, + "branch": branch, + }, + translation_validate_params.TranslationValidateParams, ), ), cast_to=TranslationValidateResponse, diff --git a/src/knock_mapi/resources/variables.py b/src/knock_mapi/resources/variables.py index 95cf4695..e34c2cd7 100644 --- a/src/knock_mapi/resources/variables.py +++ b/src/knock_mapi/resources/variables.py @@ -2,10 +2,12 @@ from __future__ import annotations +from typing_extensions import Literal + import httpx from ..types import variable_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -29,7 +31,7 @@ def with_raw_response(self) -> VariablesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return VariablesResourceWithRawResponse(self) @@ -38,26 +40,64 @@ def with_streaming_response(self) -> VariablesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return VariablesResourceWithStreamingResponse(self) + def retrieve( + self, + key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Variable: + """ + Returns a single variable by key with per-environment value overrides. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key: + raise ValueError(f"Expected a non-empty value for `key` but received {key!r}") + return self._get( + f"/v1/variables/{key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Variable, + ) + def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + limit: int | Omit = omit, + type: Literal["public", "secret"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Variable]: - """ - Returns a paginated list of variables for a given environment. + """Returns a list of variables. + + When an environment is specified, returns + per-environment variables. Otherwise, returns project-scoped variables with + per-environment overrides. Args: environment: The environment slug. @@ -66,8 +106,13 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + limit: The number of entries to fetch per-page. + type: Filter variables by type. Supports 'public' or 'secret'. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -89,7 +134,9 @@ def list( "environment": environment, "after": after, "before": before, + "branch": branch, "limit": limit, + "type": type, }, variable_list_params.VariableListParams, ), @@ -105,7 +152,7 @@ def with_raw_response(self) -> AsyncVariablesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncVariablesResourceWithRawResponse(self) @@ -114,26 +161,64 @@ def with_streaming_response(self) -> AsyncVariablesResourceWithStreamingResponse """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncVariablesResourceWithStreamingResponse(self) + async def retrieve( + self, + key: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Variable: + """ + Returns a single variable by key with per-environment value overrides. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not key: + raise ValueError(f"Expected a non-empty value for `key` but received {key!r}") + return await self._get( + f"/v1/variables/{key}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Variable, + ) + def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + limit: int | Omit = omit, + type: Literal["public", "secret"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Variable, AsyncEntriesCursor[Variable]]: - """ - Returns a paginated list of variables for a given environment. + """Returns a list of variables. + + When an environment is specified, returns + per-environment variables. Otherwise, returns project-scoped variables with + per-environment overrides. Args: environment: The environment slug. @@ -142,8 +227,13 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + limit: The number of entries to fetch per-page. + type: Filter variables by type. Supports 'public' or 'secret'. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -165,7 +255,9 @@ def list( "environment": environment, "after": after, "before": before, + "branch": branch, "limit": limit, + "type": type, }, variable_list_params.VariableListParams, ), @@ -178,6 +270,9 @@ class VariablesResourceWithRawResponse: def __init__(self, variables: VariablesResource) -> None: self._variables = variables + self.retrieve = to_raw_response_wrapper( + variables.retrieve, + ) self.list = to_raw_response_wrapper( variables.list, ) @@ -187,6 +282,9 @@ class AsyncVariablesResourceWithRawResponse: def __init__(self, variables: AsyncVariablesResource) -> None: self._variables = variables + self.retrieve = async_to_raw_response_wrapper( + variables.retrieve, + ) self.list = async_to_raw_response_wrapper( variables.list, ) @@ -196,6 +294,9 @@ class VariablesResourceWithStreamingResponse: def __init__(self, variables: VariablesResource) -> None: self._variables = variables + self.retrieve = to_streamed_response_wrapper( + variables.retrieve, + ) self.list = to_streamed_response_wrapper( variables.list, ) @@ -205,6 +306,9 @@ class AsyncVariablesResourceWithStreamingResponse: def __init__(self, variables: AsyncVariablesResource) -> None: self._variables = variables + self.retrieve = async_to_streamed_response_wrapper( + variables.retrieve, + ) self.list = async_to_streamed_response_wrapper( variables.list, ) diff --git a/src/knock_mapi/resources/workflows/steps.py b/src/knock_mapi/resources/workflows/steps.py index cebf1cfe..8a5073fc 100644 --- a/src/knock_mapi/resources/workflows/steps.py +++ b/src/knock_mapi/resources/workflows/steps.py @@ -6,7 +6,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -24,13 +24,15 @@ class StepsResource(SyncAPIResource): + """Workflows let you express your cross-channel notification logic.""" + @cached_property def with_raw_response(self) -> StepsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return StepsResourceWithRawResponse(self) @@ -39,7 +41,7 @@ def with_streaming_response(self) -> StepsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return StepsResourceWithStreamingResponse(self) @@ -50,15 +52,16 @@ def preview_template( workflow_key: str, environment: str, recipient: step_preview_template_params.Recipient, - actor: Optional[step_preview_template_params.Actor] | NotGiven = NOT_GIVEN, - data: Dict[str, object] | NotGiven = NOT_GIVEN, - tenant: Optional[str] | NotGiven = NOT_GIVEN, + branch: str | Omit = omit, + actor: Optional[step_preview_template_params.Actor] | Omit = omit, + data: Dict[str, object] | Omit = omit, + tenant: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> StepPreviewTemplateResponse: """ Generates a rendered template for a given channel step in a workflow. @@ -69,12 +72,15 @@ def preview_template( recipient: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + actor: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. data: The data to pass to the workflow template for rendering. - tenant: The tenant to associate the workflow with. + tenant: The tenant to associate the workflow with. Must not contain whitespace. extra_headers: Send extra headers @@ -105,7 +111,11 @@ def preview_template( extra_body=extra_body, timeout=timeout, query=maybe_transform( - {"environment": environment}, step_preview_template_params.StepPreviewTemplateParams + { + "environment": environment, + "branch": branch, + }, + step_preview_template_params.StepPreviewTemplateParams, ), ), cast_to=StepPreviewTemplateResponse, @@ -113,13 +123,15 @@ def preview_template( class AsyncStepsResource(AsyncAPIResource): + """Workflows let you express your cross-channel notification logic.""" + @cached_property def with_raw_response(self) -> AsyncStepsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncStepsResourceWithRawResponse(self) @@ -128,7 +140,7 @@ def with_streaming_response(self) -> AsyncStepsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncStepsResourceWithStreamingResponse(self) @@ -139,15 +151,16 @@ async def preview_template( workflow_key: str, environment: str, recipient: step_preview_template_params.Recipient, - actor: Optional[step_preview_template_params.Actor] | NotGiven = NOT_GIVEN, - data: Dict[str, object] | NotGiven = NOT_GIVEN, - tenant: Optional[str] | NotGiven = NOT_GIVEN, + branch: str | Omit = omit, + actor: Optional[step_preview_template_params.Actor] | Omit = omit, + data: Dict[str, object] | Omit = omit, + tenant: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> StepPreviewTemplateResponse: """ Generates a rendered template for a given channel step in a workflow. @@ -158,12 +171,15 @@ async def preview_template( recipient: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + actor: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. data: The data to pass to the workflow template for rendering. - tenant: The tenant to associate the workflow with. + tenant: The tenant to associate the workflow with. Must not contain whitespace. extra_headers: Send extra headers @@ -194,7 +210,11 @@ async def preview_template( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, step_preview_template_params.StepPreviewTemplateParams + { + "environment": environment, + "branch": branch, + }, + step_preview_template_params.StepPreviewTemplateParams, ), ), cast_to=StepPreviewTemplateResponse, diff --git a/src/knock_mapi/resources/workflows/workflows.py b/src/knock_mapi/resources/workflows/workflows.py index ab9d85db..55d198b7 100644 --- a/src/knock_mapi/resources/workflows/workflows.py +++ b/src/knock_mapi/resources/workflows/workflows.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional import httpx @@ -22,7 +22,7 @@ workflow_retrieve_params, workflow_validate_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -38,14 +38,18 @@ from ...types.workflow_run_response import WorkflowRunResponse from ...types.workflow_upsert_response import WorkflowUpsertResponse from ...types.workflow_activate_response import WorkflowActivateResponse +from ...types.workflow_retrieve_response import WorkflowRetrieveResponse from ...types.workflow_validate_response import WorkflowValidateResponse __all__ = ["WorkflowsResource", "AsyncWorkflowsResource"] class WorkflowsResource(SyncAPIResource): + """Workflows let you express your cross-channel notification logic.""" + @cached_property def steps(self) -> StepsResource: + """Workflows let you express your cross-channel notification logic.""" return StepsResource(self._client) @cached_property @@ -54,7 +58,7 @@ def with_raw_response(self) -> WorkflowsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return WorkflowsResourceWithRawResponse(self) @@ -63,7 +67,7 @@ def with_streaming_response(self) -> WorkflowsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return WorkflowsResourceWithStreamingResponse(self) @@ -72,15 +76,16 @@ def retrieve( workflow_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Workflow: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WorkflowRetrieveResponse: """ Retrieve a workflow by its key in a given environment. @@ -89,6 +94,9 @@ def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -113,29 +121,31 @@ def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, workflow_retrieve_params.WorkflowRetrieveParams, ), ), - cast_to=Workflow, + cast_to=WorkflowRetrieveResponse, ) def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncEntriesCursor[Workflow]: """Returns a paginated list of workflows available in a given environment. @@ -151,6 +161,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -178,6 +191,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -193,12 +207,13 @@ def activate( *, environment: str, status: bool, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowActivateResponse: """Activates (or deactivates) a workflow in a given environment. @@ -214,6 +229,9 @@ def activate( status: Whether to activate or deactivate the workflow. Set to `true` by default, which will activate the workflow. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -232,7 +250,13 @@ def activate( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"environment": environment}, workflow_activate_params.WorkflowActivateParams), + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + workflow_activate_params.WorkflowActivateParams, + ), ), cast_to=WorkflowActivateResponse, ) @@ -242,17 +266,18 @@ def run( workflow_key: str, *, environment: str, - recipients: List[workflow_run_params.Recipient], - actor: Optional[workflow_run_params.Actor] | NotGiven = NOT_GIVEN, - cancellation_key: Optional[str] | NotGiven = NOT_GIVEN, - data: Dict[str, object] | NotGiven = NOT_GIVEN, - tenant: str | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[workflow_run_params.Recipient], + branch: str | Omit = omit, + actor: Optional[workflow_run_params.Actor] | Omit = omit, + cancellation_key: Optional[str] | Omit = omit, + data: Dict[str, object] | Omit = omit, + tenant: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowRunResponse: """ Runs the latest version of a committed workflow in a given environment using the @@ -263,14 +288,20 @@ def run( recipients: A list of recipients to run the workflow for. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + actor: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. cancellation_key: A key to cancel the workflow run. - data: A map of data to be used in the workflow run. + data: A map of data to be used in the workflow run. The structure should conform to + the workflow's `trigger_data_json_schema` if one is defined. Available in + templates as `{{ data.field_name }}`. See + [trigger data validation docs](https://docs.knock.app/developer-tools/validating-trigger-data). - tenant: The tenant to associate the workflow run with. + tenant: The tenant to associate the workflow run with. Must not contain whitespace. extra_headers: Send extra headers @@ -299,7 +330,13 @@ def run( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"environment": environment}, workflow_run_params.WorkflowRunParams), + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + workflow_run_params.WorkflowRunParams, + ), ), cast_to=WorkflowRunResponse, ) @@ -310,15 +347,16 @@ def upsert( *, environment: str, workflow: workflow_upsert_params.Workflow, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowUpsertResponse: """ Updates a workflow of a given key, or creates a new one if it does not yet @@ -333,6 +371,9 @@ def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -359,6 +400,7 @@ def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -374,12 +416,13 @@ def validate( *, environment: str, workflow: workflow_validate_params.Workflow, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowValidateResponse: """Validates a workflow payload without persisting it. @@ -393,6 +436,9 @@ def validate( workflow: A workflow request for upserting a workflow. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -411,15 +457,24 @@ def validate( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"environment": environment}, workflow_validate_params.WorkflowValidateParams), + query=maybe_transform( + { + "environment": environment, + "branch": branch, + }, + workflow_validate_params.WorkflowValidateParams, + ), ), cast_to=WorkflowValidateResponse, ) class AsyncWorkflowsResource(AsyncAPIResource): + """Workflows let you express your cross-channel notification logic.""" + @cached_property def steps(self) -> AsyncStepsResource: + """Workflows let you express your cross-channel notification logic.""" return AsyncStepsResource(self._client) @cached_property @@ -428,7 +483,7 @@ def with_raw_response(self) -> AsyncWorkflowsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#accessing-raw-response-data-eg-headers """ return AsyncWorkflowsResourceWithRawResponse(self) @@ -437,7 +492,7 @@ def with_streaming_response(self) -> AsyncWorkflowsResourceWithStreamingResponse """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/stainless-sdks/knock-mapi-python#with_streaming_response + For more information, see https://www.github.com/knocklabs/knock-mgmt-python#with_streaming_response """ return AsyncWorkflowsResourceWithStreamingResponse(self) @@ -446,15 +501,16 @@ async def retrieve( workflow_key: str, *, environment: str, - annotate: bool | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Workflow: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WorkflowRetrieveResponse: """ Retrieve a workflow by its key in a given environment. @@ -463,6 +519,9 @@ async def retrieve( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -487,29 +546,31 @@ async def retrieve( { "environment": environment, "annotate": annotate, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, }, workflow_retrieve_params.WorkflowRetrieveParams, ), ), - cast_to=Workflow, + cast_to=WorkflowRetrieveResponse, ) def list( self, *, environment: str, - after: str | NotGiven = NOT_GIVEN, - annotate: bool | NotGiven = NOT_GIVEN, - before: str | NotGiven = NOT_GIVEN, - hide_uncommitted_changes: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, + after: str | Omit = omit, + annotate: bool | Omit = omit, + before: str | Omit = omit, + branch: str | Omit = omit, + hide_uncommitted_changes: bool | Omit = omit, + limit: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[Workflow, AsyncEntriesCursor[Workflow]]: """Returns a paginated list of workflows available in a given environment. @@ -525,6 +586,9 @@ def list( before: The cursor to fetch entries before. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + hide_uncommitted_changes: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. @@ -552,6 +616,7 @@ def list( "after": after, "annotate": annotate, "before": before, + "branch": branch, "hide_uncommitted_changes": hide_uncommitted_changes, "limit": limit, }, @@ -567,12 +632,13 @@ async def activate( *, environment: str, status: bool, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowActivateResponse: """Activates (or deactivates) a workflow in a given environment. @@ -588,6 +654,9 @@ async def activate( status: Whether to activate or deactivate the workflow. Set to `true` by default, which will activate the workflow. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -607,7 +676,11 @@ async def activate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, workflow_activate_params.WorkflowActivateParams + { + "environment": environment, + "branch": branch, + }, + workflow_activate_params.WorkflowActivateParams, ), ), cast_to=WorkflowActivateResponse, @@ -618,17 +691,18 @@ async def run( workflow_key: str, *, environment: str, - recipients: List[workflow_run_params.Recipient], - actor: Optional[workflow_run_params.Actor] | NotGiven = NOT_GIVEN, - cancellation_key: Optional[str] | NotGiven = NOT_GIVEN, - data: Dict[str, object] | NotGiven = NOT_GIVEN, - tenant: str | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[workflow_run_params.Recipient], + branch: str | Omit = omit, + actor: Optional[workflow_run_params.Actor] | Omit = omit, + cancellation_key: Optional[str] | Omit = omit, + data: Dict[str, object] | Omit = omit, + tenant: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowRunResponse: """ Runs the latest version of a committed workflow in a given environment using the @@ -639,14 +713,20 @@ async def run( recipients: A list of recipients to run the workflow for. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + actor: A recipient reference, used when referencing a recipient by either their ID (for a user), or by a reference for an object. cancellation_key: A key to cancel the workflow run. - data: A map of data to be used in the workflow run. + data: A map of data to be used in the workflow run. The structure should conform to + the workflow's `trigger_data_json_schema` if one is defined. Available in + templates as `{{ data.field_name }}`. See + [trigger data validation docs](https://docs.knock.app/developer-tools/validating-trigger-data). - tenant: The tenant to associate the workflow run with. + tenant: The tenant to associate the workflow run with. Must not contain whitespace. extra_headers: Send extra headers @@ -675,7 +755,13 @@ async def run( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"environment": environment}, workflow_run_params.WorkflowRunParams), + query=await async_maybe_transform( + { + "environment": environment, + "branch": branch, + }, + workflow_run_params.WorkflowRunParams, + ), ), cast_to=WorkflowRunResponse, ) @@ -686,15 +772,16 @@ async def upsert( *, environment: str, workflow: workflow_upsert_params.Workflow, - annotate: bool | NotGiven = NOT_GIVEN, - commit: bool | NotGiven = NOT_GIVEN, - commit_message: str | NotGiven = NOT_GIVEN, + annotate: bool | Omit = omit, + branch: str | Omit = omit, + commit: bool | Omit = omit, + commit_message: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowUpsertResponse: """ Updates a workflow of a given key, or creates a new one if it does not yet @@ -709,6 +796,9 @@ async def upsert( annotate: Whether to annotate the resource. Only used in the Knock CLI. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + commit: Whether to commit the resource at the same time as modifying it. commit_message: The message to commit the resource with, only used if `commit` is `true`. @@ -735,6 +825,7 @@ async def upsert( { "environment": environment, "annotate": annotate, + "branch": branch, "commit": commit, "commit_message": commit_message, }, @@ -750,12 +841,13 @@ async def validate( *, environment: str, workflow: workflow_validate_params.Workflow, + branch: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WorkflowValidateResponse: """Validates a workflow payload without persisting it. @@ -769,6 +861,9 @@ async def validate( workflow: A workflow request for upserting a workflow. + branch: The slug of a branch to use. This option can only be used when `environment` is + `"development"`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -788,7 +883,11 @@ async def validate( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"environment": environment}, workflow_validate_params.WorkflowValidateParams + { + "environment": environment, + "branch": branch, + }, + workflow_validate_params.WorkflowValidateParams, ), ), cast_to=WorkflowValidateResponse, @@ -820,6 +919,7 @@ def __init__(self, workflows: WorkflowsResource) -> None: @cached_property def steps(self) -> StepsResourceWithRawResponse: + """Workflows let you express your cross-channel notification logic.""" return StepsResourceWithRawResponse(self._workflows.steps) @@ -848,6 +948,7 @@ def __init__(self, workflows: AsyncWorkflowsResource) -> None: @cached_property def steps(self) -> AsyncStepsResourceWithRawResponse: + """Workflows let you express your cross-channel notification logic.""" return AsyncStepsResourceWithRawResponse(self._workflows.steps) @@ -876,6 +977,7 @@ def __init__(self, workflows: WorkflowsResource) -> None: @cached_property def steps(self) -> StepsResourceWithStreamingResponse: + """Workflows let you express your cross-channel notification logic.""" return StepsResourceWithStreamingResponse(self._workflows.steps) @@ -904,4 +1006,5 @@ def __init__(self, workflows: AsyncWorkflowsResource) -> None: @cached_property def steps(self) -> AsyncStepsResourceWithStreamingResponse: + """Workflows let you express your cross-channel notification logic.""" return AsyncStepsResourceWithStreamingResponse(self._workflows.steps) diff --git a/src/knock_mapi/types/__init__.py b/src/knock_mapi/types/__init__.py index 4356197d..a100cf59 100644 --- a/src/knock_mapi/types/__init__.py +++ b/src/knock_mapi/types/__init__.py @@ -2,15 +2,47 @@ from __future__ import annotations +from . import ( + workflow, + broadcast, + workflow_branch_step, + broadcast_send_response, + workflow_upsert_response, + broadcast_cancel_response, + broadcast_upsert_response, + workflow_activate_response, + workflow_retrieve_response, + workflow_validate_response, + broadcast_validate_response, +) +from .. import _compat +from .guide import Guide as Guide +from .branch import Branch as Branch from .commit import Commit as Commit -from .shared import PageInfo as PageInfo +from .member import Member as Member +from .shared import ( + PageInfo as PageInfo, + MessageTypeURLField as MessageTypeURLField, + MessageTypeJsonField as MessageTypeJsonField, + MessageTypeImageField as MessageTypeImageField, + MessageTypeButtonField as MessageTypeButtonField, + MessageTypeSelectField as MessageTypeSelectField, + MessageTypeBooleanField as MessageTypeBooleanField, + MessageTypeMarkdownField as MessageTypeMarkdownField, + MessageTypeTextareaField as MessageTypeTextareaField, + MessageTypeMultiSelectField as MessageTypeMultiSelectField, +) from .channel import Channel as Channel from .partial import Partial as Partial +from .audience import Audience as Audience from .duration import Duration as Duration from .variable import Variable as Variable from .workflow import Workflow as Workflow +from .broadcast import Broadcast as Broadcast from .condition import Condition as Condition +from .guide_step import GuideStep as GuideStep from .environment import Environment as Environment +from .member_user import MemberUser as MemberUser from .send_window import SendWindow as SendWindow from .translation import Translation as Translation from .email_layout import EmailLayout as EmailLayout @@ -24,22 +56,37 @@ from .email_template import EmailTemplate as EmailTemplate from .condition_group import ConditionGroup as ConditionGroup from .condition_param import ConditionParam as ConditionParam +from .static_audience import StaticAudience as StaticAudience +from .dynamic_audience import DynamicAudience as DynamicAudience +from .guide_step_param import GuideStepParam as GuideStepParam from .request_template import RequestTemplate as RequestTemplate from .webhook_template import WebhookTemplate as WebhookTemplate +from .guide_list_params import GuideListParams as GuideListParams from .send_window_param import SendWindowParam as SendWindowParam +from .workflow_sms_step import WorkflowSMSStep as WorkflowSMSStep +from .audience_condition import AudienceCondition as AudienceCondition +from .branch_list_params import BranchListParams as BranchListParams from .channel_group_rule import ChannelGroupRule as ChannelGroupRule from .commit_list_params import CommitListParams as CommitListParams +from .member_list_params import MemberListParams as MemberListParams from .sms_template_param import SMSTemplateParam as SMSTemplateParam +from .workflow_chat_step import WorkflowChatStep as WorkflowChatStep +from .workflow_push_step import WorkflowPushStep as WorkflowPushStep from .channel_list_params import ChannelListParams as ChannelListParams from .chat_template_param import ChatTemplateParam as ChatTemplateParam +from .guide_upsert_params import GuideUpsertParams as GuideUpsertParams from .partial_list_params import PartialListParams as PartialListParams from .push_template_param import PushTemplateParam as PushTemplateParam from .workflow_batch_step import WorkflowBatchStep as WorkflowBatchStep from .workflow_delay_step import WorkflowDelayStep as WorkflowDelayStep +from .workflow_email_step import WorkflowEmailStep as WorkflowEmailStep from .workflow_fetch_step import WorkflowFetchStep as WorkflowFetchStep from .workflow_run_params import WorkflowRunParams as WorkflowRunParams from .workflow_step_param import WorkflowStepParam as WorkflowStepParam +from .audience_list_params import AudienceListParams as AudienceListParams from .auth_verify_response import AuthVerifyResponse as AuthVerifyResponse +from .branch_create_params import BranchCreateParams as BranchCreateParams +from .branch_delete_params import BranchDeleteParams as BranchDeleteParams from .email_template_param import EmailTemplateParam as EmailTemplateParam from .in_app_feed_template import InAppFeedTemplate as InAppFeedTemplate from .message_type_variant import MessageTypeVariant as MessageTypeVariant @@ -47,39 +94,73 @@ from .variable_list_params import VariableListParams as VariableListParams from .workflow_branch_step import WorkflowBranchStep as WorkflowBranchStep from .workflow_list_params import WorkflowListParams as WorkflowListParams +from .broadcast_list_params import BroadcastListParams as BroadcastListParams +from .broadcast_send_params import BroadcastSendParams as BroadcastSendParams from .chat_channel_settings import ChatChannelSettings as ChatChannelSettings from .condition_group_param import ConditionGroupParam as ConditionGroupParam +from .guide_activate_params import GuideActivateParams as GuideActivateParams +from .guide_retrieve_params import GuideRetrieveParams as GuideRetrieveParams +from .guide_upsert_response import GuideUpsertResponse as GuideUpsertResponse +from .guide_validate_params import GuideValidateParams as GuideValidateParams from .partial_upsert_params import PartialUpsertParams as PartialUpsertParams from .push_channel_settings import PushChannelSettings as PushChannelSettings -from .workflow_channel_step import WorkflowChannelStep as WorkflowChannelStep from .workflow_run_response import WorkflowRunResponse as WorkflowRunResponse +from .workflow_webhook_step import WorkflowWebhookStep as WorkflowWebhookStep +from .audience_upsert_params import AudienceUpsertParams as AudienceUpsertParams +from .branch_retrieve_params import BranchRetrieveParams as BranchRetrieveParams from .email_channel_settings import EmailChannelSettings as EmailChannelSettings +from .guide_archive_response import GuideArchiveResponse as GuideArchiveResponse from .request_template_param import RequestTemplateParam as RequestTemplateParam from .webhook_template_param import WebhookTemplateParam as WebhookTemplateParam +from .workflow_ai_agent_step import WorkflowAIAgentStep as WorkflowAIAgentStep from .workflow_throttle_step import WorkflowThrottleStep as WorkflowThrottleStep from .workflow_upsert_params import WorkflowUpsertParams as WorkflowUpsertParams from .api_key_exchange_params import APIKeyExchangeParams as APIKeyExchangeParams +from .audience_archive_params import AudienceArchiveParams as AudienceArchiveParams +from .broadcast_cancel_params import BroadcastCancelParams as BroadcastCancelParams +from .broadcast_request_param import BroadcastRequestParam as BroadcastRequestParam +from .broadcast_send_response import BroadcastSendResponse as BroadcastSendResponse +from .broadcast_upsert_params import BroadcastUpsertParams as BroadcastUpsertParams from .environment_list_params import EnvironmentListParams as EnvironmentListParams +from .guide_activate_response import GuideActivateResponse as GuideActivateResponse +from .guide_validate_response import GuideValidateResponse as GuideValidateResponse from .message_type_text_field import MessageTypeTextField as MessageTypeTextField from .partial_retrieve_params import PartialRetrieveParams as PartialRetrieveParams from .partial_upsert_response import PartialUpsertResponse as PartialUpsertResponse from .partial_validate_params import PartialValidateParams as PartialValidateParams from .translation_list_params import TranslationListParams as TranslationListParams +from .workflow_sms_step_param import WorkflowSMSStepParam as WorkflowSMSStepParam +from .audience_condition_param import AudienceConditionParam as AudienceConditionParam +from .audience_retrieve_params import AudienceRetrieveParams as AudienceRetrieveParams +from .audience_upsert_response import AudienceUpsertResponse as AudienceUpsertResponse +from .audience_validate_params import AudienceValidateParams as AudienceValidateParams from .commit_commit_all_params import CommitCommitAllParams as CommitCommitAllParams from .email_layout_list_params import EmailLayoutListParams as EmailLayoutListParams from .message_type_list_params import MessageTypeListParams as MessageTypeListParams from .workflow_activate_params import WorkflowActivateParams as WorkflowActivateParams +from .workflow_chat_step_param import WorkflowChatStepParam as WorkflowChatStepParam +from .workflow_push_step_param import WorkflowPushStepParam as WorkflowPushStepParam from .workflow_retrieve_params import WorkflowRetrieveParams as WorkflowRetrieveParams from .workflow_upsert_response import WorkflowUpsertResponse as WorkflowUpsertResponse from .workflow_validate_params import WorkflowValidateParams as WorkflowValidateParams from .api_key_exchange_response import APIKeyExchangeResponse as APIKeyExchangeResponse +from .audience_archive_response import AudienceArchiveResponse as AudienceArchiveResponse +from .broadcast_cancel_response import BroadcastCancelResponse as BroadcastCancelResponse +from .broadcast_retrieve_params import BroadcastRetrieveParams as BroadcastRetrieveParams +from .broadcast_upsert_response import BroadcastUpsertResponse as BroadcastUpsertResponse +from .broadcast_validate_params import BroadcastValidateParams as BroadcastValidateParams from .channel_group_list_params import ChannelGroupListParams as ChannelGroupListParams from .commit_promote_all_params import CommitPromoteAllParams as CommitPromoteAllParams from .partial_validate_response import PartialValidateResponse as PartialValidateResponse from .translation_upsert_params import TranslationUpsertParams as TranslationUpsertParams from .workflow_batch_step_param import WorkflowBatchStepParam as WorkflowBatchStepParam from .workflow_delay_step_param import WorkflowDelayStepParam as WorkflowDelayStepParam +from .workflow_email_step_param import WorkflowEmailStepParam as WorkflowEmailStepParam from .workflow_fetch_step_param import WorkflowFetchStepParam as WorkflowFetchStepParam +from .workflow_in_app_feed_step import WorkflowInAppFeedStep as WorkflowInAppFeedStep +from .workflow_update_data_step import WorkflowUpdateDataStep as WorkflowUpdateDataStep +from .workflow_update_user_step import WorkflowUpdateUserStep as WorkflowUpdateUserStep +from .audience_validate_response import AudienceValidateResponse as AudienceValidateResponse from .commit_commit_all_response import CommitCommitAllResponse as CommitCommitAllResponse from .email_layout_upsert_params import EmailLayoutUpsertParams as EmailLayoutUpsertParams from .in_app_feed_template_param import InAppFeedTemplateParam as InAppFeedTemplateParam @@ -88,7 +169,10 @@ from .sms_channel_settings_param import SMSChannelSettingsParam as SMSChannelSettingsParam from .workflow_activate_response import WorkflowActivateResponse as WorkflowActivateResponse from .workflow_branch_step_param import WorkflowBranchStepParam as WorkflowBranchStepParam +from .workflow_retrieve_response import WorkflowRetrieveResponse as WorkflowRetrieveResponse from .workflow_validate_response import WorkflowValidateResponse as WorkflowValidateResponse +from .broadcast_validate_response import BroadcastValidateResponse as BroadcastValidateResponse +from .channel_group_upsert_params import ChannelGroupUpsertParams as ChannelGroupUpsertParams from .chat_channel_settings_param import ChatChannelSettingsParam as ChatChannelSettingsParam from .commit_promote_all_response import CommitPromoteAllResponse as CommitPromoteAllResponse from .commit_promote_one_response import CommitPromoteOneResponse as CommitPromoteOneResponse @@ -96,21 +180,64 @@ from .translation_retrieve_params import TranslationRetrieveParams as TranslationRetrieveParams from .translation_upsert_response import TranslationUpsertResponse as TranslationUpsertResponse from .translation_validate_params import TranslationValidateParams as TranslationValidateParams -from .workflow_channel_step_param import WorkflowChannelStepParam as WorkflowChannelStepParam +from .workflow_random_cohort_step import WorkflowRandomCohortStep as WorkflowRandomCohortStep +from .workflow_update_object_step import WorkflowUpdateObjectStep as WorkflowUpdateObjectStep +from .workflow_update_tenant_step import WorkflowUpdateTenantStep as WorkflowUpdateTenantStep +from .workflow_webhook_step_param import WorkflowWebhookStepParam as WorkflowWebhookStepParam +from .channel_environment_settings import ChannelEnvironmentSettings as ChannelEnvironmentSettings from .email_channel_settings_param import EmailChannelSettingsParam as EmailChannelSettingsParam from .email_layout_retrieve_params import EmailLayoutRetrieveParams as EmailLayoutRetrieveParams from .email_layout_upsert_response import EmailLayoutUpsertResponse as EmailLayoutUpsertResponse from .email_layout_validate_params import EmailLayoutValidateParams as EmailLayoutValidateParams +from .guide_activation_url_pattern import GuideActivationURLPattern as GuideActivationURLPattern from .in_app_feed_channel_settings import InAppFeedChannelSettings as InAppFeedChannelSettings from .message_type_retrieve_params import MessageTypeRetrieveParams as MessageTypeRetrieveParams from .message_type_upsert_response import MessageTypeUpsertResponse as MessageTypeUpsertResponse from .message_type_validate_params import MessageTypeValidateParams as MessageTypeValidateParams +from .workflow_ai_agent_step_param import WorkflowAIAgentStepParam as WorkflowAIAgentStepParam from .workflow_throttle_step_param import WorkflowThrottleStepParam as WorkflowThrottleStepParam +from .channel_group_upsert_response import ChannelGroupUpsertResponse as ChannelGroupUpsertResponse from .message_type_text_field_param import MessageTypeTextFieldParam as MessageTypeTextFieldParam from .translation_retrieve_response import TranslationRetrieveResponse as TranslationRetrieveResponse from .translation_validate_response import TranslationValidateResponse as TranslationValidateResponse from .email_layout_validate_response import EmailLayoutValidateResponse as EmailLayoutValidateResponse from .message_type_validate_response import MessageTypeValidateResponse as MessageTypeValidateResponse from .workflow_trigger_workflow_step import WorkflowTriggerWorkflowStep as WorkflowTriggerWorkflowStep +from .workflow_in_app_feed_step_param import WorkflowInAppFeedStepParam as WorkflowInAppFeedStepParam +from .workflow_update_data_step_param import WorkflowUpdateDataStepParam as WorkflowUpdateDataStepParam +from .workflow_update_user_step_param import WorkflowUpdateUserStepParam as WorkflowUpdateUserStepParam +from .workflow_random_cohort_step_param import WorkflowRandomCohortStepParam as WorkflowRandomCohortStepParam +from .workflow_update_object_step_param import WorkflowUpdateObjectStepParam as WorkflowUpdateObjectStepParam +from .workflow_update_tenant_step_param import WorkflowUpdateTenantStepParam as WorkflowUpdateTenantStepParam +from .guide_activation_url_pattern_param import GuideActivationURLPatternParam as GuideActivationURLPatternParam from .in_app_feed_channel_settings_param import InAppFeedChannelSettingsParam as InAppFeedChannelSettingsParam from .workflow_trigger_workflow_step_param import WorkflowTriggerWorkflowStepParam as WorkflowTriggerWorkflowStepParam + +# Rebuild cyclical models only after all modules are imported. +# This ensures that, when building the deferred (due to cyclical references) model schema, +# Pydantic can resolve the necessary references. +# See: https://github.com/pydantic/pydantic/issues/11250 for more context. +if _compat.PYDANTIC_V1: + workflow.Workflow.update_forward_refs() # type: ignore + workflow_branch_step.WorkflowBranchStep.update_forward_refs() # type: ignore + workflow_retrieve_response.WorkflowRetrieveResponse.update_forward_refs() # type: ignore + workflow_activate_response.WorkflowActivateResponse.update_forward_refs() # type: ignore + workflow_upsert_response.WorkflowUpsertResponse.update_forward_refs() # type: ignore + workflow_validate_response.WorkflowValidateResponse.update_forward_refs() # type: ignore + broadcast.Broadcast.update_forward_refs() # type: ignore + broadcast_cancel_response.BroadcastCancelResponse.update_forward_refs() # type: ignore + broadcast_send_response.BroadcastSendResponse.update_forward_refs() # type: ignore + broadcast_upsert_response.BroadcastUpsertResponse.update_forward_refs() # type: ignore + broadcast_validate_response.BroadcastValidateResponse.update_forward_refs() # type: ignore +else: + workflow.Workflow.model_rebuild(_parent_namespace_depth=0) + workflow_branch_step.WorkflowBranchStep.model_rebuild(_parent_namespace_depth=0) + workflow_retrieve_response.WorkflowRetrieveResponse.model_rebuild(_parent_namespace_depth=0) + workflow_activate_response.WorkflowActivateResponse.model_rebuild(_parent_namespace_depth=0) + workflow_upsert_response.WorkflowUpsertResponse.model_rebuild(_parent_namespace_depth=0) + workflow_validate_response.WorkflowValidateResponse.model_rebuild(_parent_namespace_depth=0) + broadcast.Broadcast.model_rebuild(_parent_namespace_depth=0) + broadcast_cancel_response.BroadcastCancelResponse.model_rebuild(_parent_namespace_depth=0) + broadcast_send_response.BroadcastSendResponse.model_rebuild(_parent_namespace_depth=0) + broadcast_upsert_response.BroadcastUpsertResponse.model_rebuild(_parent_namespace_depth=0) + broadcast_validate_response.BroadcastValidateResponse.model_rebuild(_parent_namespace_depth=0) diff --git a/src/knock_mapi/types/api_key_exchange_response.py b/src/knock_mapi/types/api_key_exchange_response.py index 73f229f7..1db31e01 100644 --- a/src/knock_mapi/types/api_key_exchange_response.py +++ b/src/knock_mapi/types/api_key_exchange_response.py @@ -6,5 +6,7 @@ class APIKeyExchangeResponse(BaseModel): + """Returns an API key that can be used to make requests to the public API.""" + api_key: str """The secret API key exchanged from the service token.""" diff --git a/src/knock_mapi/types/audience.py b/src/knock_mapi/types/audience.py new file mode 100644 index 00000000..072c3b7e --- /dev/null +++ b/src/knock_mapi/types/audience.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union +from typing_extensions import Annotated, TypeAlias + +from .._utils import PropertyInfo +from .static_audience import StaticAudience +from .dynamic_audience import DynamicAudience + +__all__ = ["Audience"] + +Audience: TypeAlias = Annotated[Union[StaticAudience, DynamicAudience], PropertyInfo(discriminator="type")] diff --git a/src/knock_mapi/types/audience_archive_params.py b/src/knock_mapi/types/audience_archive_params.py new file mode 100644 index 00000000..dc73de29 --- /dev/null +++ b/src/knock_mapi/types/audience_archive_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AudienceArchiveParams"] + + +class AudienceArchiveParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" diff --git a/src/knock_mapi/types/audience_archive_response.py b/src/knock_mapi/types/audience_archive_response.py new file mode 100644 index 00000000..096fbdaf --- /dev/null +++ b/src/knock_mapi/types/audience_archive_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["AudienceArchiveResponse"] + + +class AudienceArchiveResponse(BaseModel): + """The response from archiving an audience.""" + + result: str + """The result of the archive operation.""" diff --git a/src/knock_mapi/types/audience_condition.py b/src/knock_mapi/types/audience_condition.py new file mode 100644 index 00000000..78bf5168 --- /dev/null +++ b/src/knock_mapi/types/audience_condition.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AudienceCondition"] + + +class AudienceCondition(BaseModel): + """A condition to evaluate for audience membership.""" + + operator: Literal[ + "equal_to", + "not_equal_to", + "greater_than", + "less_than", + "greater_than_or_equal_to", + "less_than_or_equal_to", + "contains", + "not_contains", + "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", + "empty", + "not_empty", + "exists", + "not_exists", + "is_timestamp", + "is_audience_member", + "is_not_audience_member", + ] + """The operator to use when evaluating the condition.""" + + property: str + """The property to be evaluated. + + Properties are dynamic values using path expressions like `recipient.plan` or + `recipient.created_at`. + """ + + argument: Optional[str] = None + """The argument to compare against. + + Can be a static value (string, number, boolean) or a dynamic path expression. + """ diff --git a/src/knock_mapi/types/audience_condition_param.py b/src/knock_mapi/types/audience_condition_param.py new file mode 100644 index 00000000..cede471e --- /dev/null +++ b/src/knock_mapi/types/audience_condition_param.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["AudienceConditionParam"] + + +class AudienceConditionParam(TypedDict, total=False): + """A condition to evaluate for audience membership.""" + + operator: Required[ + Literal[ + "equal_to", + "not_equal_to", + "greater_than", + "less_than", + "greater_than_or_equal_to", + "less_than_or_equal_to", + "contains", + "not_contains", + "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", + "empty", + "not_empty", + "exists", + "not_exists", + "is_timestamp", + "is_audience_member", + "is_not_audience_member", + ] + ] + """The operator to use when evaluating the condition.""" + + property: Required[str] + """The property to be evaluated. + + Properties are dynamic values using path expressions like `recipient.plan` or + `recipient.created_at`. + """ + + argument: Optional[str] + """The argument to compare against. + + Can be a static value (string, number, boolean) or a dynamic path expression. + """ diff --git a/src/knock_mapi/types/audience_list_params.py b/src/knock_mapi/types/audience_list_params.py new file mode 100644 index 00000000..59cc480b --- /dev/null +++ b/src/knock_mapi/types/audience_list_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AudienceListParams"] + + +class AudienceListParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + after: str + """The cursor to fetch entries after.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + before: str + """The cursor to fetch entries before.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ + + limit: int + """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/audience_retrieve_params.py b/src/knock_mapi/types/audience_retrieve_params.py new file mode 100644 index 00000000..040703f1 --- /dev/null +++ b/src/knock_mapi/types/audience_retrieve_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AudienceRetrieveParams"] + + +class AudienceRetrieveParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ diff --git a/src/knock_mapi/types/audience_upsert_params.py b/src/knock_mapi/types/audience_upsert_params.py new file mode 100644 index 00000000..f917c1e0 --- /dev/null +++ b/src/knock_mapi/types/audience_upsert_params.py @@ -0,0 +1,84 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .audience_condition_param import AudienceConditionParam + +__all__ = [ + "AudienceUpsertParams", + "Audience", + "AudienceStaticAudienceRequest", + "AudienceDynamicAudienceRequest", + "AudienceDynamicAudienceRequestSegment", +] + + +class AudienceUpsertParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + audience: Required[Audience] + """An audience object with attributes to create or update an audience. + + Use `type: static` for audiences with explicitly managed members, or + `type: dynamic` for audiences with segment-based membership. + """ + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + commit: bool + """Whether to commit the resource at the same time as modifying it.""" + + commit_message: str + """The message to commit the resource with, only used if `commit` is `true`.""" + + +class AudienceStaticAudienceRequest(TypedDict, total=False): + """Request body for creating/updating a static audience.""" + + name: Required[str] + """The name of the audience.""" + + type: Required[Literal["static"]] + """The type of audience. Set to `static` for static audiences.""" + + description: Optional[str] + """A description of the audience.""" + + +class AudienceDynamicAudienceRequestSegment(TypedDict, total=False): + conditions: Required[Iterable[AudienceConditionParam]] + """A list of conditions within this segment, joined by AND.""" + + +class AudienceDynamicAudienceRequest(TypedDict, total=False): + """Request body for creating/updating a dynamic audience.""" + + name: Required[str] + """The name of the audience.""" + + type: Required[Literal["dynamic"]] + """The type of audience. Set to `dynamic` for dynamic audiences.""" + + description: Optional[str] + """A description of the audience.""" + + segments: Iterable[AudienceDynamicAudienceRequestSegment] + """A list of segments that define the dynamic audience membership criteria. + + Each segment contains one or more conditions joined by AND. Multiple segments + are joined by OR. + """ + + +Audience: TypeAlias = Union[AudienceStaticAudienceRequest, AudienceDynamicAudienceRequest] diff --git a/src/knock_mapi/types/audience_upsert_response.py b/src/knock_mapi/types/audience_upsert_response.py new file mode 100644 index 00000000..4e9c65b8 --- /dev/null +++ b/src/knock_mapi/types/audience_upsert_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel +from .audience import Audience + +__all__ = ["AudienceUpsertResponse"] + + +class AudienceUpsertResponse(BaseModel): + """Wraps the Audience response under the `audience` key.""" + + audience: Audience + """An audience defines a set of users that can be targeted for notifications. + + Can be either a `StaticAudience` (members explicitly added/removed) or a + `DynamicAudience` (members determined by segment conditions). + """ diff --git a/src/knock_mapi/types/audience_validate_params.py b/src/knock_mapi/types/audience_validate_params.py new file mode 100644 index 00000000..308ad655 --- /dev/null +++ b/src/knock_mapi/types/audience_validate_params.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .audience_condition_param import AudienceConditionParam + +__all__ = [ + "AudienceValidateParams", + "Audience", + "AudienceStaticAudienceRequest", + "AudienceDynamicAudienceRequest", + "AudienceDynamicAudienceRequestSegment", +] + + +class AudienceValidateParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + audience: Required[Audience] + """An audience object with attributes to create or update an audience. + + Use `type: static` for audiences with explicitly managed members, or + `type: dynamic` for audiences with segment-based membership. + """ + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +class AudienceStaticAudienceRequest(TypedDict, total=False): + """Request body for creating/updating a static audience.""" + + name: Required[str] + """The name of the audience.""" + + type: Required[Literal["static"]] + """The type of audience. Set to `static` for static audiences.""" + + description: Optional[str] + """A description of the audience.""" + + +class AudienceDynamicAudienceRequestSegment(TypedDict, total=False): + conditions: Required[Iterable[AudienceConditionParam]] + """A list of conditions within this segment, joined by AND.""" + + +class AudienceDynamicAudienceRequest(TypedDict, total=False): + """Request body for creating/updating a dynamic audience.""" + + name: Required[str] + """The name of the audience.""" + + type: Required[Literal["dynamic"]] + """The type of audience. Set to `dynamic` for dynamic audiences.""" + + description: Optional[str] + """A description of the audience.""" + + segments: Iterable[AudienceDynamicAudienceRequestSegment] + """A list of segments that define the dynamic audience membership criteria. + + Each segment contains one or more conditions joined by AND. Multiple segments + are joined by OR. + """ + + +Audience: TypeAlias = Union[AudienceStaticAudienceRequest, AudienceDynamicAudienceRequest] diff --git a/src/knock_mapi/types/audience_validate_response.py b/src/knock_mapi/types/audience_validate_response.py new file mode 100644 index 00000000..c27ce49b --- /dev/null +++ b/src/knock_mapi/types/audience_validate_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel +from .audience import Audience + +__all__ = ["AudienceValidateResponse"] + + +class AudienceValidateResponse(BaseModel): + """Wraps the Audience response under the `audience` key.""" + + audience: Audience + """An audience defines a set of users that can be targeted for notifications. + + Can be either a `StaticAudience` (members explicitly added/removed) or a + `DynamicAudience` (members determined by segment conditions). + """ diff --git a/src/knock_mapi/types/auth_verify_response.py b/src/knock_mapi/types/auth_verify_response.py index 4f2620c9..36c691b0 100644 --- a/src/knock_mapi/types/auth_verify_response.py +++ b/src/knock_mapi/types/auth_verify_response.py @@ -1,13 +1,97 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional +from typing_extensions import Literal + from .._models import BaseModel -__all__ = ["AuthVerifyResponse"] +__all__ = ["AuthVerifyResponse", "AccountFeatures"] + + +class AccountFeatures(BaseModel): + """Account plan features and limits.""" + + batch_items_render_limit_allowed: Optional[bool] = None + """Whether batch rendering limits can be configured.""" + + custom_branding_allowed: Optional[bool] = None + """Whether custom branding can be applied to notifications.""" + + data_retention_days: Optional[int] = None + """Number of days data is retained, null for unlimited retention.""" + + data_warehouse_extension_allowed: Optional[bool] = None + """Whether data warehouse integration extensions are available.""" + + datadog_extension_allowed: Optional[bool] = None + """Whether Datadog integration extension is available.""" + + dsync_allowed: Optional[bool] = None + """Whether directory sync functionality is available.""" + + guides_monthly_notified_recipients_limit: Optional[int] = None + """Monthly limit for guide notification recipients, null for unlimited.""" + + guides_per_tenant_scope_allowed: Optional[bool] = None + """Whether per-tenant scope for guide messages is allowed.""" + + heap_extension_allowed: Optional[bool] = None + """Whether Heap integration extension is available.""" + + knock_branding_required: Optional[bool] = None + """Whether Knock branding is required to be displayed.""" + + litmus_email_preview_allowed: Optional[bool] = None + """Whether Litmus email preview integration is available.""" + + message_sent_limit: Optional[int] = None + """Monthly limit for messages sent, null for unlimited.""" + + new_relic_extension_allowed: Optional[bool] = None + """Whether New Relic integration extension is available.""" + + segment_extension_allowed: Optional[bool] = None + """Whether Segment integration extension is available.""" + + self_serve_allowed: Optional[bool] = None + """Whether self-service account management features are available.""" + + sso_allowed: Optional[bool] = None + """Whether single sign-on (SSO) is enabled for the account.""" + + tenant_preferences_allowed: Optional[bool] = None + """Whether tenant-level preferences are supported.""" + + translations_allowed: Optional[bool] = None + """Whether multi-language translations are supported.""" class AuthVerifyResponse(BaseModel): + """Information about the current calling scope.""" + + account_features: AccountFeatures + """Account plan features and limits.""" + account_name: str + """The display name of the account.""" account_slug: str + """The unique slug identifier for the account.""" + + type: Literal["service_token", "oauth_context"] + """ + The type of authentication context - either a service token or OAuth user + context. + """ + + service_token_name: Optional[str] = None + """ + The name of the service token if authenticated via service token, null for OAuth + contexts. + """ - service_token_name: str + user_id: Optional[str] = None + """ + The ID of the authenticated user if in OAuth context, null for service token + contexts. + """ diff --git a/src/knock_mapi/types/branch.py b/src/knock_mapi/types/branch.py new file mode 100644 index 00000000..b79bb5f9 --- /dev/null +++ b/src/knock_mapi/types/branch.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["Branch"] + + +class Branch(BaseModel): + """A branch object.""" + + created_at: datetime + """The timestamp of when the branch was created.""" + + slug: str + """A unique slug for the branch. Cannot exceed 255 characters.""" + + updated_at: datetime + """The timestamp of when the branch was last updated.""" + + deleted_at: Optional[datetime] = None + """The timestamp of when the branch was deleted.""" + + last_commit_at: Optional[datetime] = None + """The timestamp of the most-recent commit in the branch.""" diff --git a/src/knock_mapi/types/branch_create_params.py b/src/knock_mapi/types/branch_create_params.py new file mode 100644 index 00000000..2277bdb6 --- /dev/null +++ b/src/knock_mapi/types/branch_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BranchCreateParams"] + + +class BranchCreateParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" diff --git a/src/knock_mapi/types/branch_delete_params.py b/src/knock_mapi/types/branch_delete_params.py new file mode 100644 index 00000000..6b5b6013 --- /dev/null +++ b/src/knock_mapi/types/branch_delete_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BranchDeleteParams"] + + +class BranchDeleteParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" diff --git a/src/knock_mapi/types/branch_list_params.py b/src/knock_mapi/types/branch_list_params.py new file mode 100644 index 00000000..b5d0fd1b --- /dev/null +++ b/src/knock_mapi/types/branch_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BranchListParams"] + + +class BranchListParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + after: str + """The cursor to fetch entries after.""" + + before: str + """The cursor to fetch entries before.""" + + limit: int + """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/branch_retrieve_params.py b/src/knock_mapi/types/branch_retrieve_params.py new file mode 100644 index 00000000..e0a98e57 --- /dev/null +++ b/src/knock_mapi/types/branch_retrieve_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BranchRetrieveParams"] + + +class BranchRetrieveParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" diff --git a/src/knock_mapi/types/broadcast.py b/src/knock_mapi/types/broadcast.py new file mode 100644 index 00000000..eb998a46 --- /dev/null +++ b/src/knock_mapi/types/broadcast.py @@ -0,0 +1,111 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel +from .workflow_sms_step import WorkflowSMSStep +from .workflow_chat_step import WorkflowChatStep +from .workflow_push_step import WorkflowPushStep +from .workflow_delay_step import WorkflowDelayStep +from .workflow_email_step import WorkflowEmailStep +from .workflow_webhook_step import WorkflowWebhookStep +from .workflow_in_app_feed_step import WorkflowInAppFeedStep +from .workflow_random_cohort_step import WorkflowRandomCohortStep + +__all__ = ["Broadcast", "Step", "Settings"] + +Step: TypeAlias = Union[ + WorkflowWebhookStep, + WorkflowInAppFeedStep, + WorkflowChatStep, + WorkflowSMSStep, + WorkflowPushStep, + WorkflowEmailStep, + "WorkflowBranchStep", + WorkflowDelayStep, + WorkflowRandomCohortStep, +] + + +class Settings(BaseModel): + """A map of broadcast settings.""" + + is_commercial: Optional[bool] = None + """Whether the broadcast is commercial. Defaults to true.""" + + override_preferences: Optional[bool] = None + """Whether to ignore recipient preferences for a given type of notification. + + If true, will send for every channel in the workflow even if the recipient has + opted out of a certain kind. Defaults to false. + """ + + +class Broadcast(BaseModel): + """A broadcast object.""" + + created_at: datetime + """The timestamp of when the broadcast was created. (read-only).""" + + environment: str + """The slug of the environment in which the broadcast exists. (read-only).""" + + key: str + """The unique key string for the broadcast object. + + Must be at minimum 3 characters and at maximum 255 characters in length. Must be + in the format of ^[a-z0-9_-]+$. + """ + + name: str + """A name for the broadcast. Must be at maximum 255 characters in length.""" + + sha: str + """The SHA hash of the workflow data. (read-only).""" + + status: Literal["draft", "scheduled", "sent"] + """The current status of the broadcast. One of: `draft`, `scheduled`, `sent`.""" + + steps: List[Step] + """A list of broadcast step objects in the broadcast. + + Broadcasts only support channel, branch, and delay steps. + """ + + updated_at: datetime + """The timestamp of when the broadcast was last updated. (read-only).""" + + valid: bool + """Whether the broadcast and its steps are in a valid state. (read-only).""" + + archived_at: Optional[datetime] = None + """The timestamp of when the broadcast was archived.""" + + categories: Optional[List[str]] = None + """A list of categories that the broadcast belongs to.""" + + description: Optional[str] = None + """An arbitrary string attached to a broadcast object. + + Useful for adding notes about the broadcast for internal purposes. Maximum of + 280 characters allowed. + """ + + scheduled_at: Optional[datetime] = None + """The timestamp of when the broadcast is scheduled to be sent.""" + + sent_at: Optional[datetime] = None + """The timestamp of when the broadcast was sent. (read-only).""" + + settings: Optional[Settings] = None + """A map of broadcast settings.""" + + target_audience_key: Optional[str] = None + """The key of the audience to target for this broadcast.""" + + +from .workflow_branch_step import WorkflowBranchStep diff --git a/src/knock_mapi/types/broadcast_cancel_params.py b/src/knock_mapi/types/broadcast_cancel_params.py new file mode 100644 index 00000000..cda2c710 --- /dev/null +++ b/src/knock_mapi/types/broadcast_cancel_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BroadcastCancelParams"] + + +class BroadcastCancelParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ diff --git a/src/knock_mapi/types/broadcast_cancel_response.py b/src/knock_mapi/types/broadcast_cancel_response.py new file mode 100644 index 00000000..9bd25653 --- /dev/null +++ b/src/knock_mapi/types/broadcast_cancel_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .._models import BaseModel + +__all__ = ["BroadcastCancelResponse"] + + +class BroadcastCancelResponse(BaseModel): + """Wraps the Broadcast response under the `broadcast` key.""" + + broadcast: "Broadcast" + """A broadcast object.""" + + +from .broadcast import Broadcast diff --git a/src/knock_mapi/types/broadcast_list_params.py b/src/knock_mapi/types/broadcast_list_params.py new file mode 100644 index 00000000..b9f606a4 --- /dev/null +++ b/src/knock_mapi/types/broadcast_list_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BroadcastListParams"] + + +class BroadcastListParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + after: str + """The cursor to fetch entries after.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + before: str + """The cursor to fetch entries before.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ + + limit: int + """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/broadcast_request_param.py b/src/knock_mapi/types/broadcast_request_param.py new file mode 100644 index 00000000..1d3eedbd --- /dev/null +++ b/src/knock_mapi/types/broadcast_request_param.py @@ -0,0 +1,81 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from datetime import datetime +from typing_extensions import Required, Annotated, TypeAlias, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo +from .workflow_sms_step_param import WorkflowSMSStepParam +from .workflow_chat_step_param import WorkflowChatStepParam +from .workflow_push_step_param import WorkflowPushStepParam +from .workflow_delay_step_param import WorkflowDelayStepParam +from .workflow_email_step_param import WorkflowEmailStepParam +from .workflow_webhook_step_param import WorkflowWebhookStepParam +from .workflow_in_app_feed_step_param import WorkflowInAppFeedStepParam +from .workflow_random_cohort_step_param import WorkflowRandomCohortStepParam + +__all__ = ["BroadcastRequestParam", "Step", "Settings"] + +Step: TypeAlias = Union[ + WorkflowWebhookStepParam, + WorkflowInAppFeedStepParam, + WorkflowChatStepParam, + WorkflowSMSStepParam, + WorkflowPushStepParam, + WorkflowEmailStepParam, + "WorkflowBranchStepParam", + WorkflowDelayStepParam, + WorkflowRandomCohortStepParam, +] + + +class Settings(TypedDict, total=False): + """A map of broadcast settings.""" + + is_commercial: bool + """Whether the broadcast is commercial. Defaults to true.""" + + override_preferences: bool + """Whether to ignore recipient preferences for a given type of notification. + + If true, will send for every channel in the workflow even if the recipient has + opted out of a certain kind. Defaults to false. + """ + + +class BroadcastRequestParam(TypedDict, total=False): + """A broadcast request for upserting a broadcast.""" + + name: Required[str] + """A name for the broadcast. Must be at maximum 255 characters in length.""" + + steps: Required[Iterable[Step]] + """A list of broadcast step objects in the broadcast. + + Broadcasts only support channel, branch, and delay steps. + """ + + categories: SequenceNotStr[str] + """A list of categories that the broadcast belongs to.""" + + description: str + """An arbitrary string attached to a broadcast object. + + Useful for adding notes about the broadcast for internal purposes. Maximum of + 280 characters allowed. + """ + + scheduled_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The timestamp of when the broadcast is scheduled to be sent.""" + + settings: Settings + """A map of broadcast settings.""" + + target_audience_key: str + """The key of the audience to target for this broadcast.""" + + +from .workflow_branch_step_param import WorkflowBranchStepParam diff --git a/src/knock_mapi/types/broadcast_retrieve_params.py b/src/knock_mapi/types/broadcast_retrieve_params.py new file mode 100644 index 00000000..0cba386d --- /dev/null +++ b/src/knock_mapi/types/broadcast_retrieve_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BroadcastRetrieveParams"] + + +class BroadcastRetrieveParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ diff --git a/src/knock_mapi/types/broadcast_send_params.py b/src/knock_mapi/types/broadcast_send_params.py new file mode 100644 index 00000000..92705844 --- /dev/null +++ b/src/knock_mapi/types/broadcast_send_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["BroadcastSendParams"] + + +class BroadcastSendParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + send_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """When to send the broadcast. + + If provided, the broadcast will be scheduled to send at this time. Must be in + ISO 8601 UTC format. If not provided, the broadcast will be sent immediately. + """ diff --git a/src/knock_mapi/types/broadcast_send_response.py b/src/knock_mapi/types/broadcast_send_response.py new file mode 100644 index 00000000..03f77ca7 --- /dev/null +++ b/src/knock_mapi/types/broadcast_send_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .._models import BaseModel + +__all__ = ["BroadcastSendResponse"] + + +class BroadcastSendResponse(BaseModel): + """Wraps the Broadcast response under the `broadcast` key.""" + + broadcast: "Broadcast" + """A broadcast object.""" + + +from .broadcast import Broadcast diff --git a/src/knock_mapi/types/broadcast_upsert_params.py b/src/knock_mapi/types/broadcast_upsert_params.py new file mode 100644 index 00000000..87003d01 --- /dev/null +++ b/src/knock_mapi/types/broadcast_upsert_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BroadcastUpsertParams"] + + +class BroadcastUpsertParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + broadcast: Required["BroadcastRequestParam"] + """A broadcast request for upserting a broadcast.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +from .broadcast_request_param import BroadcastRequestParam diff --git a/src/knock_mapi/types/broadcast_upsert_response.py b/src/knock_mapi/types/broadcast_upsert_response.py new file mode 100644 index 00000000..006457ae --- /dev/null +++ b/src/knock_mapi/types/broadcast_upsert_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .._models import BaseModel + +__all__ = ["BroadcastUpsertResponse"] + + +class BroadcastUpsertResponse(BaseModel): + """Wraps the Broadcast response under the `broadcast` key.""" + + broadcast: "Broadcast" + """A broadcast object.""" + + +from .broadcast import Broadcast diff --git a/src/knock_mapi/types/broadcast_validate_params.py b/src/knock_mapi/types/broadcast_validate_params.py new file mode 100644 index 00000000..45eae549 --- /dev/null +++ b/src/knock_mapi/types/broadcast_validate_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["BroadcastValidateParams"] + + +class BroadcastValidateParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + broadcast: Required["BroadcastRequestParam"] + """A broadcast request for upserting a broadcast.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +from .broadcast_request_param import BroadcastRequestParam diff --git a/src/knock_mapi/types/broadcast_validate_response.py b/src/knock_mapi/types/broadcast_validate_response.py new file mode 100644 index 00000000..f5a74301 --- /dev/null +++ b/src/knock_mapi/types/broadcast_validate_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .._models import BaseModel + +__all__ = ["BroadcastValidateResponse"] + + +class BroadcastValidateResponse(BaseModel): + """Wraps the Broadcast response under the `broadcast` key.""" + + broadcast: "Broadcast" + """A broadcast object.""" + + +from .broadcast import Broadcast diff --git a/src/knock_mapi/types/channel.py b/src/knock_mapi/types/channel.py index 87fd918e..4c786236 100644 --- a/src/knock_mapi/types/channel.py +++ b/src/knock_mapi/types/channel.py @@ -1,15 +1,21 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Dict, Optional from datetime import datetime from typing_extensions import Literal from .._models import BaseModel +from .channel_environment_settings import ChannelEnvironmentSettings __all__ = ["Channel"] class Channel(BaseModel): + """A configured channel, which is a way to route messages to a provider.""" + + id: str + """The unique identifier for the channel.""" + created_at: datetime """The timestamp of when the channel was created.""" @@ -43,3 +49,10 @@ class Channel(BaseModel): description: Optional[str] = None """Optional description of the channel's purpose or usage.""" + + environment_settings: Optional[Dict[str, ChannelEnvironmentSettings]] = None + """ + Per-environment settings for this channel, keyed by environment slug (e.g., + 'development', 'production'). Only included when requested via the `include` + parameter or when retrieving a single channel. + """ diff --git a/src/knock_mapi/types/channel_environment_settings.py b/src/knock_mapi/types/channel_environment_settings.py new file mode 100644 index 00000000..8efdf78c --- /dev/null +++ b/src/knock_mapi/types/channel_environment_settings.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from .._models import BaseModel + +__all__ = ["ChannelEnvironmentSettings"] + + +class ChannelEnvironmentSettings(BaseModel): + """Environment-specific settings for a channel.""" + + id: str + """The unique identifier for these environment settings.""" + + is_sandbox: bool + """Whether the channel is in sandbox mode for this environment. + + Sandbox mode may prevent actual message delivery. + """ + + is_valid: bool + """ + Whether the channel configuration is valid and ready to send messages in this + environment. + """ + + channel_settings: Optional[Dict[str, object]] = None + """Channel-type-specific settings (e.g., from_address for email). + + Structure varies by channel type. + """ + + provider_settings: Optional[Dict[str, object]] = None + """Provider-specific settings (e.g., API keys, credentials). + + Structure varies by provider. Secret values are obfuscated unless they are + Liquid templates. + """ diff --git a/src/knock_mapi/types/channel_group.py b/src/knock_mapi/types/channel_group.py index 1a242902..5292db48 100644 --- a/src/knock_mapi/types/channel_group.py +++ b/src/knock_mapi/types/channel_group.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from datetime import datetime from typing_extensions import Literal @@ -11,6 +11,8 @@ class ChannelGroup(BaseModel): + """A group of channels with rules for when they are applicable.""" + channel_rules: List[ChannelGroupRule] """Rules for determining which channels should be used.""" @@ -40,3 +42,6 @@ class ChannelGroup(BaseModel): updated_at: datetime """The timestamp of when the channel group was last updated.""" + + archived_at: Optional[datetime] = None + """The timestamp of when the channel group was archived (soft deleted).""" diff --git a/src/knock_mapi/types/channel_group_rule.py b/src/knock_mapi/types/channel_group_rule.py index 993120fa..4887f18b 100644 --- a/src/knock_mapi/types/channel_group_rule.py +++ b/src/knock_mapi/types/channel_group_rule.py @@ -11,6 +11,10 @@ class ChannelGroupRule(BaseModel): + """ + A rule that determines if a channel should be executed as part of a channel group. + """ + channel: Channel """A configured channel, which is a way to route messages to a provider.""" @@ -43,8 +47,16 @@ class ChannelGroupRule(BaseModel): "contains", "not_contains", "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", "empty", "not_empty", + "exists", + "not_exists", + "is_timestamp", "is_audience_member", "is_not_audience_member", ] diff --git a/src/knock_mapi/types/channel_group_upsert_params.py b/src/knock_mapi/types/channel_group_upsert_params.py new file mode 100644 index 00000000..bf2208b6 --- /dev/null +++ b/src/knock_mapi/types/channel_group_upsert_params.py @@ -0,0 +1,83 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChannelGroupUpsertParams", "ChannelGroup", "ChannelGroupChannelRule"] + + +class ChannelGroupUpsertParams(TypedDict, total=False): + channel_group: Required[ChannelGroup] + """A request to create or update a channel group.""" + + +class ChannelGroupChannelRule(TypedDict, total=False): + """ + A rule that determines if a channel should be executed as part of a channel group. + """ + + channel_key: Required[str] + """The key of the channel this rule applies to.""" + + rule_type: Required[Literal["if", "unless", "always"]] + """ + The type of rule (if = conditional, unless = negative conditional, always = + always apply). + """ + + argument: Optional[str] + """For conditional rules, the value to compare against.""" + + index: int + """The order index of this rule within the channel group.""" + + operator: Optional[ + Literal[ + "equal_to", + "not_equal_to", + "greater_than", + "less_than", + "greater_than_or_equal_to", + "less_than_or_equal_to", + "contains", + "not_contains", + "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", + "empty", + "not_empty", + "exists", + "not_exists", + "is_timestamp", + "is_audience_member", + "is_not_audience_member", + ] + ] + """For conditional rules, the operator to apply.""" + + variable: Optional[str] + """For conditional rules, the variable to evaluate.""" + + +class ChannelGroup(TypedDict, total=False): + """A request to create or update a channel group.""" + + channel_type: Required[Literal["email", "in_app", "in_app_feed", "in_app_guide", "sms", "push", "chat", "http"]] + """The type of channels contained in this group.""" + + name: Required[str] + """The human-readable name of the channel group.""" + + channel_rules: Iterable[ChannelGroupChannelRule] + """Rules for determining which channels should be used.""" + + operator: Literal["any", "all"] + """ + Determines how the channel rules are applied ('any' means any rule can match, + 'all' means all rules must match). + """ diff --git a/src/knock_mapi/types/channel_group_upsert_response.py b/src/knock_mapi/types/channel_group_upsert_response.py new file mode 100644 index 00000000..81cbf634 --- /dev/null +++ b/src/knock_mapi/types/channel_group_upsert_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel +from .channel_group import ChannelGroup + +__all__ = ["ChannelGroupUpsertResponse"] + + +class ChannelGroupUpsertResponse(BaseModel): + """Wraps the ChannelGroup response under the `channel_group` key.""" + + channel_group: ChannelGroup + """A group of channels with rules for when they are applicable.""" diff --git a/src/knock_mapi/types/channel_list_params.py b/src/knock_mapi/types/channel_list_params.py index 3136ca78..8780fd7b 100644 --- a/src/knock_mapi/types/channel_list_params.py +++ b/src/knock_mapi/types/channel_list_params.py @@ -2,17 +2,27 @@ from __future__ import annotations -from typing_extensions import TypedDict +from typing import List +from typing_extensions import Literal, TypedDict __all__ = ["ChannelListParams"] class ChannelListParams(TypedDict, total=False): + id: str + """A channel id to filter the results by.""" + after: str """The cursor to fetch entries after.""" before: str """The cursor to fetch entries before.""" + include: List[Literal["environment_settings"]] + """Associated resources to include in the response. + + Accepts `environment_settings` to include per-environment channel configuration. + """ + limit: int """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/chat_channel_settings.py b/src/knock_mapi/types/chat_channel_settings.py index 09b9d1ea..f5c2047a 100644 --- a/src/knock_mapi/types/chat_channel_settings.py +++ b/src/knock_mapi/types/chat_channel_settings.py @@ -8,6 +8,11 @@ class ChatChannelSettings(BaseModel): + """Chat channel settings. + + Only used as configuration as part of a workflow channel step. + """ + email_based_user_id_resolution: Optional[bool] = None """Whether to resolve chat provider user IDs using a Knock user's email address. diff --git a/src/knock_mapi/types/chat_channel_settings_param.py b/src/knock_mapi/types/chat_channel_settings_param.py index b631453f..264c8da5 100644 --- a/src/knock_mapi/types/chat_channel_settings_param.py +++ b/src/knock_mapi/types/chat_channel_settings_param.py @@ -8,6 +8,11 @@ class ChatChannelSettingsParam(TypedDict, total=False): + """Chat channel settings. + + Only used as configuration as part of a workflow channel step. + """ + email_based_user_id_resolution: bool """Whether to resolve chat provider user IDs using a Knock user's email address. diff --git a/src/knock_mapi/types/chat_template.py b/src/knock_mapi/types/chat_template.py index e237e15f..12ba6faa 100644 --- a/src/knock_mapi/types/chat_template.py +++ b/src/knock_mapi/types/chat_template.py @@ -8,6 +8,8 @@ class ChatTemplate(BaseModel): + """A chat template.""" + markdown_body: str """The markdown body of the chat template.""" diff --git a/src/knock_mapi/types/chat_template_param.py b/src/knock_mapi/types/chat_template_param.py index ed1f1ece..94222a33 100644 --- a/src/knock_mapi/types/chat_template_param.py +++ b/src/knock_mapi/types/chat_template_param.py @@ -9,6 +9,8 @@ class ChatTemplateParam(TypedDict, total=False): + """A chat template.""" + markdown_body: Required[str] """The markdown body of the chat template.""" diff --git a/src/knock_mapi/types/commit.py b/src/knock_mapi/types/commit.py index 30ab531c..198db352 100644 --- a/src/knock_mapi/types/commit.py +++ b/src/knock_mapi/types/commit.py @@ -6,10 +6,12 @@ from .._models import BaseModel -__all__ = ["Commit", "CommitAuthor", "Resource"] +__all__ = ["Commit", "Author", "Resource"] -class CommitAuthor(BaseModel): +class Author(BaseModel): + """The author of the commit.""" + email: str """The email address of the commit author.""" @@ -18,23 +20,24 @@ class CommitAuthor(BaseModel): class Resource(BaseModel): + """The resource object associated with the commit.""" + identifier: str """The unique identifier for the resource.""" - type: Literal["email_layout", "workflow", "translation", "partial", "message_type"] + type: Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"] """The type of the resource object.""" class Commit(BaseModel): + """A commit is a change to a resource within an environment, made by an author.""" + id: str """The unique identifier for the commit.""" - commit_author: CommitAuthor + author: Author """The author of the commit.""" - commit_message: str - """The optional message about the commit.""" - created_at: datetime """The timestamp of when the commit was created.""" @@ -44,5 +47,5 @@ class Commit(BaseModel): resource: Resource """The resource object associated with the commit.""" - updated_at: datetime - """The timestamp of when the commit was last updated.""" + commit_message: Optional[str] = None + """The optional message about the commit.""" diff --git a/src/knock_mapi/types/commit_commit_all_params.py b/src/knock_mapi/types/commit_commit_all_params.py index d5331d49..c1f1f637 100644 --- a/src/knock_mapi/types/commit_commit_all_params.py +++ b/src/knock_mapi/types/commit_commit_all_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import List, Union +from typing_extensions import Literal, Required, TypedDict __all__ = ["CommitCommitAllParams"] @@ -11,5 +12,27 @@ class CommitCommitAllParams(TypedDict, total=False): environment: Required[str] """The environment slug.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit_message: str """An optional message to include in a commit.""" + + resource_id: str + """Filter changes to commit by resource identifier. + + Must be used together with resource_type. + """ + + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + """Filter changes to commit by resource type(s). + + Accepts a single type or array of types. Can be combined with resource_id to + filter for specific resources. + """ diff --git a/src/knock_mapi/types/commit_commit_all_response.py b/src/knock_mapi/types/commit_commit_all_response.py index 656b04ef..a837d952 100644 --- a/src/knock_mapi/types/commit_commit_all_response.py +++ b/src/knock_mapi/types/commit_commit_all_response.py @@ -6,5 +6,7 @@ class CommitCommitAllResponse(BaseModel): + """The response from committing all changes.""" + result: str """The result of the commit operation.""" diff --git a/src/knock_mapi/types/commit_list_params.py b/src/knock_mapi/types/commit_list_params.py index 26b5b3c9..ae8ada12 100644 --- a/src/knock_mapi/types/commit_list_params.py +++ b/src/knock_mapi/types/commit_list_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import List, Union +from typing_extensions import Literal, Required, TypedDict __all__ = ["CommitListParams"] @@ -17,6 +18,12 @@ class CommitListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + limit: int """The number of entries to fetch per-page.""" @@ -25,3 +32,21 @@ class CommitListParams(TypedDict, total=False): Whether to show commits in the given environment that have not been promoted to the subsequent environment (false) or commits which have been promoted (true). """ + + resource_id: str + """Filter commits by resource identifier. + + Must be used together with resource_type. For most resources, this will be the + resource key. In the case of translations, this will be the locale code and + namespace, separated by a `/`. For example, `en/courses` or `en`. + """ + + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + """Filter commits by resource type(s). + + Accepts a single type or array of types. Can be combined with resource_id to + filter for specific resources. + """ diff --git a/src/knock_mapi/types/commit_promote_all_params.py b/src/knock_mapi/types/commit_promote_all_params.py index 22b7fb2f..3cb5201c 100644 --- a/src/knock_mapi/types/commit_promote_all_params.py +++ b/src/knock_mapi/types/commit_promote_all_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import List, Union +from typing_extensions import Literal, Required, TypedDict __all__ = ["CommitPromoteAllParams"] @@ -17,5 +18,25 @@ class CommitPromoteAllParams(TypedDict, total=False): “production” (in that order), setting this param to “production” will promote all commits not currently in production from staging. - Note: This must be a non-development environment. + When this param is set to `"development"`, the `"branch"` param must be + provided. + """ + + branch: str + """The slug of the branch to promote all changes from.""" + + resource_id: str + """Filter commits to promote by resource identifier. + + Must be used together with resource_type. + """ + + resource_type: Union[ + Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"], + List[Literal["audience", "email_layout", "guide", "message_type", "partial", "translation", "workflow"]], + ] + """Filter commits to promote by resource type(s). + + Accepts a single type or array of types. Can be combined with resource_id to + filter for specific resources. """ diff --git a/src/knock_mapi/types/commit_promote_all_response.py b/src/knock_mapi/types/commit_promote_all_response.py index 865d3657..2c57c969 100644 --- a/src/knock_mapi/types/commit_promote_all_response.py +++ b/src/knock_mapi/types/commit_promote_all_response.py @@ -6,5 +6,7 @@ class CommitPromoteAllResponse(BaseModel): + """The response from promoting all changes.""" + result: str """The result of the promote operation.""" diff --git a/src/knock_mapi/types/commit_promote_one_response.py b/src/knock_mapi/types/commit_promote_one_response.py index 2c1d921a..156412a6 100644 --- a/src/knock_mapi/types/commit_promote_one_response.py +++ b/src/knock_mapi/types/commit_promote_one_response.py @@ -7,5 +7,7 @@ class CommitPromoteOneResponse(BaseModel): + """Wraps the Commit response under the `commit` key.""" + commit: Commit """A commit is a change to a resource within an environment, made by an author.""" diff --git a/src/knock_mapi/types/condition.py b/src/knock_mapi/types/condition.py index b5ee7042..d94e9214 100644 --- a/src/knock_mapi/types/condition.py +++ b/src/knock_mapi/types/condition.py @@ -9,6 +9,8 @@ class Condition(BaseModel): + """A condition to be evaluated.""" + operator: Literal[ "equal_to", "not_equal_to", @@ -19,8 +21,16 @@ class Condition(BaseModel): "contains", "not_contains", "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", "empty", "not_empty", + "exists", + "not_exists", + "is_timestamp", "is_audience_member", "is_not_audience_member", ] diff --git a/src/knock_mapi/types/condition_group.py b/src/knock_mapi/types/condition_group.py index 5505a1a5..a39665b7 100644 --- a/src/knock_mapi/types/condition_group.py +++ b/src/knock_mapi/types/condition_group.py @@ -16,11 +16,15 @@ class ConditionGroupAllMatch(BaseModel): + """A group of conditions that must all be met.""" + all: Optional[List[Condition]] = None """A list of conditions.""" class ConditionGroupAnyMatchAnyConditionGroupAllMatch(BaseModel): + """A group of conditions that must all be met.""" + all: Optional[List[Condition]] = None """A list of conditions.""" @@ -29,6 +33,8 @@ class ConditionGroupAnyMatchAnyConditionGroupAllMatch(BaseModel): class ConditionGroupAnyMatch(BaseModel): + """A group of conditions that any must be met. Can contain nested alls.""" + any: Optional[List[ConditionGroupAnyMatchAny]] = None """An array of conditions or nested condition groups to evaluate.""" diff --git a/src/knock_mapi/types/condition_group_param.py b/src/knock_mapi/types/condition_group_param.py index 0c8e37fb..63a41975 100644 --- a/src/knock_mapi/types/condition_group_param.py +++ b/src/knock_mapi/types/condition_group_param.py @@ -17,11 +17,15 @@ class ConditionGroupAllMatch(TypedDict, total=False): + """A group of conditions that must all be met.""" + all: Iterable[ConditionParam] """A list of conditions.""" class ConditionGroupAnyMatchAnyConditionGroupAllMatch(TypedDict, total=False): + """A group of conditions that must all be met.""" + all: Iterable[ConditionParam] """A list of conditions.""" @@ -30,6 +34,8 @@ class ConditionGroupAnyMatchAnyConditionGroupAllMatch(TypedDict, total=False): class ConditionGroupAnyMatch(TypedDict, total=False): + """A group of conditions that any must be met. Can contain nested alls.""" + any: Iterable[ConditionGroupAnyMatchAny] """An array of conditions or nested condition groups to evaluate.""" diff --git a/src/knock_mapi/types/condition_param.py b/src/knock_mapi/types/condition_param.py index ad44a1ab..8bb004f3 100644 --- a/src/knock_mapi/types/condition_param.py +++ b/src/knock_mapi/types/condition_param.py @@ -9,6 +9,8 @@ class ConditionParam(TypedDict, total=False): + """A condition to be evaluated.""" + operator: Required[ Literal[ "equal_to", @@ -20,8 +22,16 @@ class ConditionParam(TypedDict, total=False): "contains", "not_contains", "contains_all", + "not_contains_all", + "is_timestamp_before", + "is_timestamp_on_or_after", + "is_timestamp_between", + "is_between", "empty", "not_empty", + "exists", + "not_exists", + "is_timestamp", "is_audience_member", "is_not_audience_member", ] diff --git a/src/knock_mapi/types/duration.py b/src/knock_mapi/types/duration.py index 0d4cbf4c..66eda89d 100644 --- a/src/knock_mapi/types/duration.py +++ b/src/knock_mapi/types/duration.py @@ -8,6 +8,8 @@ class Duration(BaseModel): + """A duration of time, represented as a unit and a value.""" + unit: Literal["minutes", "hours", "days", "weeks", "months"] """The unit of time.""" diff --git a/src/knock_mapi/types/duration_param.py b/src/knock_mapi/types/duration_param.py index 0fde3d84..e48f0444 100644 --- a/src/knock_mapi/types/duration_param.py +++ b/src/knock_mapi/types/duration_param.py @@ -8,6 +8,8 @@ class DurationParam(TypedDict, total=False): + """A duration of time, represented as a unit and a value.""" + unit: Required[Literal["minutes", "hours", "days", "weeks", "months"]] """The unit of time.""" diff --git a/src/knock_mapi/types/dynamic_audience.py b/src/knock_mapi/types/dynamic_audience.py new file mode 100644 index 00000000..e89f6313 --- /dev/null +++ b/src/knock_mapi/types/dynamic_audience.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .audience_condition import AudienceCondition + +__all__ = ["DynamicAudience", "Segment"] + + +class Segment(BaseModel): + conditions: List[AudienceCondition] + """A list of conditions within this segment, joined by AND.""" + + +class DynamicAudience(BaseModel): + """ + A dynamic audience where membership is determined by segment conditions evaluated at runtime. + """ + + created_at: datetime + """The timestamp of when the audience was created.""" + + environment: str + """The slug of the environment in which the audience exists.""" + + key: str + """The unique key of the audience.""" + + name: str + """The name of the audience.""" + + segments: List[Segment] + """A list of segments that define the dynamic audience membership criteria. + + Each segment contains one or more conditions joined by AND. Multiple segments + are joined by OR. + """ + + type: Literal["dynamic"] + """The type of audience. Always `dynamic` for dynamic audiences.""" + + updated_at: datetime + """The timestamp of when the audience was last updated.""" + + description: Optional[str] = None + """A description of the audience.""" + + sha: Optional[str] = None + """The SHA hash of the audience data.""" diff --git a/src/knock_mapi/types/email_channel_settings.py b/src/knock_mapi/types/email_channel_settings.py index 89d76b0c..a9ea988d 100644 --- a/src/knock_mapi/types/email_channel_settings.py +++ b/src/knock_mapi/types/email_channel_settings.py @@ -8,6 +8,11 @@ class EmailChannelSettings(BaseModel): + """Email channel settings. + + Only used as configuration as part of a workflow channel step. + """ + bcc_address: Optional[str] = None """The BCC address on email notifications. Supports liquid.""" diff --git a/src/knock_mapi/types/email_channel_settings_param.py b/src/knock_mapi/types/email_channel_settings_param.py index 93e36e6e..5cac2151 100644 --- a/src/knock_mapi/types/email_channel_settings_param.py +++ b/src/knock_mapi/types/email_channel_settings_param.py @@ -9,6 +9,11 @@ class EmailChannelSettingsParam(TypedDict, total=False): + """Email channel settings. + + Only used as configuration as part of a workflow channel step. + """ + bcc_address: Optional[str] """The BCC address on email notifications. Supports liquid.""" diff --git a/src/knock_mapi/types/email_layout.py b/src/knock_mapi/types/email_layout.py index a03ec571..43d9d600 100644 --- a/src/knock_mapi/types/email_layout.py +++ b/src/knock_mapi/types/email_layout.py @@ -17,11 +17,13 @@ class FooterLink(BaseModel): class EmailLayout(BaseModel): + """A versioned email layout used within an environment.""" + created_at: datetime """The timestamp of when the email layout was created.""" html_layout: str - """The complete HTML content of the email layout.""" + """The complete HTML or MJML content of the email layout.""" key: str """The unique key for this email layout.""" @@ -41,5 +43,11 @@ class EmailLayout(BaseModel): footer_links: Optional[List[FooterLink]] = None """A list of one or more items to show in the footer of the email layout.""" + is_mjml: Optional[bool] = None + """Whether this layout uses MJML format. + + When true, html_layout must contain tags. + """ + updated_at: Optional[datetime] = None """The timestamp of when the email layout was last updated.""" diff --git a/src/knock_mapi/types/email_layout_list_params.py b/src/knock_mapi/types/email_layout_list_params.py index 8157a27f..599130bf 100644 --- a/src/knock_mapi/types/email_layout_list_params.py +++ b/src/knock_mapi/types/email_layout_list_params.py @@ -20,6 +20,12 @@ class EmailLayoutListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/email_layout_retrieve_params.py b/src/knock_mapi/types/email_layout_retrieve_params.py index df77534a..77c421c2 100644 --- a/src/knock_mapi/types/email_layout_retrieve_params.py +++ b/src/knock_mapi/types/email_layout_retrieve_params.py @@ -14,6 +14,12 @@ class EmailLayoutRetrieveParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/email_layout_upsert_params.py b/src/knock_mapi/types/email_layout_upsert_params.py index b95f63d0..e75775aa 100644 --- a/src/knock_mapi/types/email_layout_upsert_params.py +++ b/src/knock_mapi/types/email_layout_upsert_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable +from typing import Iterable, Optional from typing_extensions import Required, TypedDict __all__ = ["EmailLayoutUpsertParams", "EmailLayout", "EmailLayoutFooterLink"] @@ -18,6 +18,12 @@ class EmailLayoutUpsertParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit: bool """Whether to commit the resource at the same time as modifying it.""" @@ -34,8 +40,10 @@ class EmailLayoutFooterLink(TypedDict, total=False): class EmailLayout(TypedDict, total=False): + """A request to update or create an email layout.""" + html_layout: Required[str] - """The complete HTML content of the email layout.""" + """The complete HTML or MJML content of the email layout.""" name: Required[str] """The friendly name of this email layout.""" @@ -45,3 +53,9 @@ class EmailLayout(TypedDict, total=False): footer_links: Iterable[EmailLayoutFooterLink] """A list of one or more items to show in the footer of the email layout.""" + + is_mjml: Optional[bool] + """Whether this layout uses MJML format. + + When true, html_layout must contain tags. + """ diff --git a/src/knock_mapi/types/email_layout_upsert_response.py b/src/knock_mapi/types/email_layout_upsert_response.py index 186f4bbd..b1b3e92c 100644 --- a/src/knock_mapi/types/email_layout_upsert_response.py +++ b/src/knock_mapi/types/email_layout_upsert_response.py @@ -7,5 +7,7 @@ class EmailLayoutUpsertResponse(BaseModel): + """Wraps the EmailLayout response under the `email_layout` key.""" + email_layout: EmailLayout """A versioned email layout used within an environment.""" diff --git a/src/knock_mapi/types/email_layout_validate_params.py b/src/knock_mapi/types/email_layout_validate_params.py index 4a764cf2..17910bf6 100644 --- a/src/knock_mapi/types/email_layout_validate_params.py +++ b/src/knock_mapi/types/email_layout_validate_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable +from typing import Iterable, Optional from typing_extensions import Required, TypedDict __all__ = ["EmailLayoutValidateParams", "EmailLayout", "EmailLayoutFooterLink"] @@ -15,6 +15,12 @@ class EmailLayoutValidateParams(TypedDict, total=False): email_layout: Required[EmailLayout] """A request to update or create an email layout.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + class EmailLayoutFooterLink(TypedDict, total=False): text: Required[str] @@ -25,8 +31,10 @@ class EmailLayoutFooterLink(TypedDict, total=False): class EmailLayout(TypedDict, total=False): + """A request to update or create an email layout.""" + html_layout: Required[str] - """The complete HTML content of the email layout.""" + """The complete HTML or MJML content of the email layout.""" name: Required[str] """The friendly name of this email layout.""" @@ -36,3 +44,9 @@ class EmailLayout(TypedDict, total=False): footer_links: Iterable[EmailLayoutFooterLink] """A list of one or more items to show in the footer of the email layout.""" + + is_mjml: Optional[bool] + """Whether this layout uses MJML format. + + When true, html_layout must contain tags. + """ diff --git a/src/knock_mapi/types/email_layout_validate_response.py b/src/knock_mapi/types/email_layout_validate_response.py index 28d1a290..adb35015 100644 --- a/src/knock_mapi/types/email_layout_validate_response.py +++ b/src/knock_mapi/types/email_layout_validate_response.py @@ -7,5 +7,7 @@ class EmailLayoutValidateResponse(BaseModel): + """Wraps the EmailLayout response under the `email_layout` key.""" + email_layout: EmailLayout """A versioned email layout used within an environment.""" diff --git a/src/knock_mapi/types/email_template.py b/src/knock_mapi/types/email_template.py index 86b2ac5a..9c3ef406 100644 --- a/src/knock_mapi/types/email_template.py +++ b/src/knock_mapi/types/email_template.py @@ -17,6 +17,7 @@ "VisualBlockEmailDividerBlock", "VisualBlockEmailDividerBlockLayoutAttrs", "VisualBlockEmailHTMLBlock", + "VisualBlockEmailHTMLBlockLayoutAttrs", "VisualBlockEmailImageBlock", "VisualBlockEmailImageBlockLayoutAttrs", "VisualBlockEmailImageBlockStyleAttrs", @@ -28,23 +29,35 @@ class Settings(BaseModel): + """ + The [settings](https://docs.knock.app/integrations/email/settings) for the email template. Must be supplied with at least `layout_key`. + """ + attachment_key: Optional[str] = None """ - The object path in the data payload (of the workflow trigger call) to resolve - attachments. + The object path in the workflow trigger's `data` payload to resolve + attachments.Defaults to `attachments`. """ layout_key: Optional[str] = None - """The key of the email layout which the step is using.""" + """ + The `key` of the + [email layout](https://docs.knock.app/integrations/email/layouts) that wraps the + email template. When omitted, the email template will need to define the + `` structure. + """ pre_content: Optional[str] = None """ - A liquid template that will be injected into the layout above the message - template content. + A liquid template that will be injected into the email layout above the message + template content. Useful for setting variables that should be available to the + email layout. """ class VisualBlockEmailButtonSetBlockButtonSizeAttrs(BaseModel): + """The size attributes of the button.""" + is_fullwidth: Optional[bool] = None """Whether the button is full width.""" @@ -53,6 +66,8 @@ class VisualBlockEmailButtonSetBlockButtonSizeAttrs(BaseModel): class VisualBlockEmailButtonSetBlockButtonStyleAttrs(BaseModel): + """The style attributes of the button.""" + background_color: Optional[str] = None """The background color of the button.""" @@ -70,13 +85,15 @@ class VisualBlockEmailButtonSetBlockButtonStyleAttrs(BaseModel): class VisualBlockEmailButtonSetBlockButton(BaseModel): + """A button in a button set block.""" + action: str """The action of the button.""" label: str """The label of the button.""" - variant: str + variant: Literal["solid", "outline"] """The variant of the button.""" size_attrs: Optional[VisualBlockEmailButtonSetBlockButtonSizeAttrs] = None @@ -87,6 +104,8 @@ class VisualBlockEmailButtonSetBlockButton(BaseModel): class VisualBlockEmailButtonSetBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + column_gap: int """The column_gap layout attribute of the block.""" @@ -107,23 +126,31 @@ class VisualBlockEmailButtonSetBlockLayoutAttrs(BaseModel): class VisualBlockEmailButtonSetBlock(BaseModel): - id: str - """The ID of the block.""" + """A button set block in an email template.""" buttons: List[VisualBlockEmailButtonSetBlockButton] """A list of buttons in the button set.""" - type: str + type: Literal["button_set"] """The type of the block.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" layout_attrs: Optional[VisualBlockEmailButtonSetBlockLayoutAttrs] = None """The layout attributes of the block.""" + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailDividerBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + padding_bottom: int """The padding_bottom layout attribute of the block.""" @@ -138,34 +165,74 @@ class VisualBlockEmailDividerBlockLayoutAttrs(BaseModel): class VisualBlockEmailDividerBlock(BaseModel): - id: str - """The ID of the block.""" + """A divider block in an email template.""" - type: str + type: Literal["divider"] """The type of the block.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" layout_attrs: Optional[VisualBlockEmailDividerBlockLayoutAttrs] = None """The layout attributes of the block.""" + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + + +class VisualBlockEmailHTMLBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + + padding_bottom: int + """The padding_bottom layout attribute of the block.""" + + padding_left: int + """The padding_left layout attribute of the block.""" + + padding_right: int + """The padding_right layout attribute of the block.""" + + padding_top: int + """The padding_top layout attribute of the block.""" + class VisualBlockEmailHTMLBlock(BaseModel): - id: str - """The ID of the block.""" + """An HTML block in an email template.""" content: str - """The HTML content of the block.""" + """The HTML content of the block. - type: str + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. + """ + + type: Literal["html"] """The type of the block.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" + + layout_attrs: Optional[VisualBlockEmailHTMLBlockLayoutAttrs] = None + """The layout attributes of the block.""" + + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ class VisualBlockEmailImageBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + horizontal_align: Literal["left", "center", "right"] """The horizontal alignment of the block.""" @@ -183,22 +250,23 @@ class VisualBlockEmailImageBlockLayoutAttrs(BaseModel): class VisualBlockEmailImageBlockStyleAttrs(BaseModel): + """The style attributes of the image.""" + width: Optional[str] = None """The width of the image.""" class VisualBlockEmailImageBlock(BaseModel): - id: str - """The ID of the block.""" + """An image block in an email template.""" - type: str + type: Literal["image"] """The type of the block.""" url: str """The URL of the image to display.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" action: Optional[str] = None """Optional action URL for the image.""" @@ -212,8 +280,17 @@ class VisualBlockEmailImageBlock(BaseModel): style_attrs: Optional[VisualBlockEmailImageBlockStyleAttrs] = None """The style attributes of the image.""" + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailMarkdownBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + padding_bottom: int """The padding_bottom layout attribute of the block.""" @@ -228,26 +305,41 @@ class VisualBlockEmailMarkdownBlockLayoutAttrs(BaseModel): class VisualBlockEmailMarkdownBlock(BaseModel): - id: str - """The ID of the block.""" + """A markdown block in an email template.""" content: str - """The markdown content of the block.""" + """The markdown content of the block. - type: str - """The type of the block.""" + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. + """ - variant: str - """The flavor of markdown to use for the block.""" + type: Literal["markdown"] + """The type of the block.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" layout_attrs: Optional[VisualBlockEmailMarkdownBlockLayoutAttrs] = None """The layout attributes of the block.""" + variant: Optional[Literal["default"]] = None + """The flavor of markdown to use for the block.""" + + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailPartialBlockLayoutAttrs(BaseModel): + """The layout attributes of the block.""" + padding_bottom: int """The padding_bottom layout attribute of the block.""" @@ -262,8 +354,9 @@ class VisualBlockEmailPartialBlockLayoutAttrs(BaseModel): class VisualBlockEmailPartialBlock(BaseModel): - id: str - """The ID of the block.""" + """ + A partial block in an email template, used to render a reusable partial component. + """ attrs: Dict[str, object] """The attributes to pass to the partial block.""" @@ -274,15 +367,22 @@ class VisualBlockEmailPartialBlock(BaseModel): name: str """The name of the partial block.""" - type: str + type: Literal["partial"] """The type of the block.""" - version: int - """The version of the block.""" + id: Optional[str] = None + """The ID of the block.""" layout_attrs: Optional[VisualBlockEmailPartialBlockLayoutAttrs] = None """The layout attributes of the block.""" + version: Optional[int] = None + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + VisualBlock: TypeAlias = Union[ VisualBlockEmailButtonSetBlock, @@ -295,29 +395,49 @@ class VisualBlockEmailPartialBlock(BaseModel): class EmailTemplate(BaseModel): - subject: str - """The subject of the email.""" + """An email message template.""" - html_body: Optional[str] = None - """An HTML template for the email body. + settings: Settings + """ + The [settings](https://docs.knock.app/integrations/email/settings) for the email + template. Must be supplied with at least `layout_key`. + """ - Either `html_body` or `visual_blocks` must be provided. + subject: str + """The subject of the email. + + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. """ - settings: Optional[Settings] = None + html_body: Optional[str] = None + """An HTML or MJML template for the email body. + + **Required** if `visual_blocks` is not provided. Only one of `html_body` or + `visual_blocks` should be set. When `is_mjml` is true, this must contain MJML + components. Supports Liquid templating with variables like + `{{ recipient.name }}`, `{{ actor.name }}`, `{{ vars.app_name }}`, + `{{ data.custom_field }}`, and `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. """ - The [settings](https://docs.knock.app/integrations/email/settings) for the email - template. + + is_mjml: Optional[bool] = None + """Whether this template uses MJML format. + + When true, the template content will be compiled from MJML to HTML. Only valid + when the selected layout is also MJML or when no layout is selected. """ text_body: Optional[str] = None """A text template for the email body. - Only present if opted out from autogenerating it from the HTML template. + When omitted, the email template will be autogenerated from the `html_body` or + `visual_blocks`. """ visual_blocks: Optional[List[VisualBlock]] = None - """The visual blocks of the email. - - Either `html_body` or `visual_blocks` must be provided. - """ + """The visual blocks that make up the email template.""" diff --git a/src/knock_mapi/types/email_template_param.py b/src/knock_mapi/types/email_template_param.py index 5170b52e..e7a392ab 100644 --- a/src/knock_mapi/types/email_template_param.py +++ b/src/knock_mapi/types/email_template_param.py @@ -17,6 +17,7 @@ "VisualBlockEmailDividerBlock", "VisualBlockEmailDividerBlockLayoutAttrs", "VisualBlockEmailHTMLBlock", + "VisualBlockEmailHTMLBlockLayoutAttrs", "VisualBlockEmailImageBlock", "VisualBlockEmailImageBlockLayoutAttrs", "VisualBlockEmailImageBlockStyleAttrs", @@ -28,23 +29,35 @@ class Settings(TypedDict, total=False): + """ + The [settings](https://docs.knock.app/integrations/email/settings) for the email template. Must be supplied with at least `layout_key`. + """ + attachment_key: Optional[str] """ - The object path in the data payload (of the workflow trigger call) to resolve - attachments. + The object path in the workflow trigger's `data` payload to resolve + attachments.Defaults to `attachments`. """ layout_key: Optional[str] - """The key of the email layout which the step is using.""" + """ + The `key` of the + [email layout](https://docs.knock.app/integrations/email/layouts) that wraps the + email template. When omitted, the email template will need to define the + `` structure. + """ pre_content: Optional[str] """ - A liquid template that will be injected into the layout above the message - template content. + A liquid template that will be injected into the email layout above the message + template content. Useful for setting variables that should be available to the + email layout. """ class VisualBlockEmailButtonSetBlockButtonSizeAttrs(TypedDict, total=False): + """The size attributes of the button.""" + is_fullwidth: bool """Whether the button is full width.""" @@ -53,6 +66,8 @@ class VisualBlockEmailButtonSetBlockButtonSizeAttrs(TypedDict, total=False): class VisualBlockEmailButtonSetBlockButtonStyleAttrs(TypedDict, total=False): + """The style attributes of the button.""" + background_color: str """The background color of the button.""" @@ -70,13 +85,15 @@ class VisualBlockEmailButtonSetBlockButtonStyleAttrs(TypedDict, total=False): class VisualBlockEmailButtonSetBlockButton(TypedDict, total=False): + """A button in a button set block.""" + action: Required[str] """The action of the button.""" label: Required[str] """The label of the button.""" - variant: Required[str] + variant: Required[Literal["solid", "outline"]] """The variant of the button.""" size_attrs: VisualBlockEmailButtonSetBlockButtonSizeAttrs @@ -87,6 +104,8 @@ class VisualBlockEmailButtonSetBlockButton(TypedDict, total=False): class VisualBlockEmailButtonSetBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + column_gap: Required[int] """The column_gap layout attribute of the block.""" @@ -107,23 +126,31 @@ class VisualBlockEmailButtonSetBlockLayoutAttrs(TypedDict, total=False): class VisualBlockEmailButtonSetBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """A button set block in an email template.""" buttons: Required[Iterable[VisualBlockEmailButtonSetBlockButton]] """A list of buttons in the button set.""" - type: Required[str] + type: Required[Literal["button_set"]] """The type of the block.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" layout_attrs: VisualBlockEmailButtonSetBlockLayoutAttrs """The layout attributes of the block.""" + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailDividerBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + padding_bottom: Required[int] """The padding_bottom layout attribute of the block.""" @@ -138,34 +165,74 @@ class VisualBlockEmailDividerBlockLayoutAttrs(TypedDict, total=False): class VisualBlockEmailDividerBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """A divider block in an email template.""" - type: Required[str] + type: Required[Literal["divider"]] """The type of the block.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" layout_attrs: VisualBlockEmailDividerBlockLayoutAttrs """The layout attributes of the block.""" + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + + +class VisualBlockEmailHTMLBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + + padding_bottom: Required[int] + """The padding_bottom layout attribute of the block.""" + + padding_left: Required[int] + """The padding_left layout attribute of the block.""" + + padding_right: Required[int] + """The padding_right layout attribute of the block.""" + + padding_top: Required[int] + """The padding_top layout attribute of the block.""" + class VisualBlockEmailHTMLBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """An HTML block in an email template.""" content: Required[str] - """The HTML content of the block.""" + """The HTML content of the block. - type: Required[str] + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. + """ + + type: Required[Literal["html"]] """The type of the block.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" + + layout_attrs: VisualBlockEmailHTMLBlockLayoutAttrs + """The layout attributes of the block.""" + + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ class VisualBlockEmailImageBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + horizontal_align: Required[Literal["left", "center", "right"]] """The horizontal alignment of the block.""" @@ -183,22 +250,23 @@ class VisualBlockEmailImageBlockLayoutAttrs(TypedDict, total=False): class VisualBlockEmailImageBlockStyleAttrs(TypedDict, total=False): + """The style attributes of the image.""" + width: str """The width of the image.""" class VisualBlockEmailImageBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """An image block in an email template.""" - type: Required[str] + type: Required[Literal["image"]] """The type of the block.""" url: Required[str] """The URL of the image to display.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" action: Optional[str] """Optional action URL for the image.""" @@ -212,8 +280,17 @@ class VisualBlockEmailImageBlock(TypedDict, total=False): style_attrs: VisualBlockEmailImageBlockStyleAttrs """The style attributes of the image.""" + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailMarkdownBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + padding_bottom: Required[int] """The padding_bottom layout attribute of the block.""" @@ -228,26 +305,41 @@ class VisualBlockEmailMarkdownBlockLayoutAttrs(TypedDict, total=False): class VisualBlockEmailMarkdownBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """A markdown block in an email template.""" content: Required[str] - """The markdown content of the block.""" + """The markdown content of the block. - type: Required[str] - """The type of the block.""" + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. + """ - variant: Required[str] - """The flavor of markdown to use for the block.""" + type: Required[Literal["markdown"]] + """The type of the block.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" layout_attrs: VisualBlockEmailMarkdownBlockLayoutAttrs """The layout attributes of the block.""" + variant: Literal["default"] + """The flavor of markdown to use for the block.""" + + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + class VisualBlockEmailPartialBlockLayoutAttrs(TypedDict, total=False): + """The layout attributes of the block.""" + padding_bottom: Required[int] """The padding_bottom layout attribute of the block.""" @@ -262,8 +354,9 @@ class VisualBlockEmailPartialBlockLayoutAttrs(TypedDict, total=False): class VisualBlockEmailPartialBlock(TypedDict, total=False): - id: Required[str] - """The ID of the block.""" + """ + A partial block in an email template, used to render a reusable partial component. + """ attrs: Required[Dict[str, object]] """The attributes to pass to the partial block.""" @@ -274,15 +367,22 @@ class VisualBlockEmailPartialBlock(TypedDict, total=False): name: Required[str] """The name of the partial block.""" - type: Required[str] + type: Required[Literal["partial"]] """The type of the block.""" - version: Required[int] - """The version of the block.""" + id: str + """The ID of the block.""" layout_attrs: VisualBlockEmailPartialBlockLayoutAttrs """The layout attributes of the block.""" + version: int + """The version of the block schema. + + This is automatically managed by Knock and should not be set manually. Currently + all blocks are at version 1. + """ + VisualBlock: TypeAlias = Union[ VisualBlockEmailButtonSetBlock, @@ -295,29 +395,49 @@ class VisualBlockEmailPartialBlock(TypedDict, total=False): class EmailTemplateParam(TypedDict, total=False): - subject: Required[str] - """The subject of the email.""" + """An email message template.""" - html_body: str - """An HTML template for the email body. + settings: Required[Settings] + """ + The [settings](https://docs.knock.app/integrations/email/settings) for the email + template. Must be supplied with at least `layout_key`. + """ - Either `html_body` or `visual_blocks` must be provided. + subject: Required[str] + """The subject of the email. + + Supports Liquid templating with variables like `{{ recipient.name }}`, + `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and + `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. """ - settings: Optional[Settings] + html_body: Optional[str] + """An HTML or MJML template for the email body. + + **Required** if `visual_blocks` is not provided. Only one of `html_body` or + `visual_blocks` should be set. When `is_mjml` is true, this must contain MJML + components. Supports Liquid templating with variables like + `{{ recipient.name }}`, `{{ actor.name }}`, `{{ vars.app_name }}`, + `{{ data.custom_field }}`, and `{{ tenant.name }}`. See the + [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) + for available variables. """ - The [settings](https://docs.knock.app/integrations/email/settings) for the email - template. + + is_mjml: Optional[bool] + """Whether this template uses MJML format. + + When true, the template content will be compiled from MJML to HTML. Only valid + when the selected layout is also MJML or when no layout is selected. """ text_body: Optional[str] """A text template for the email body. - Only present if opted out from autogenerating it from the HTML template. + When omitted, the email template will be autogenerated from the `html_body` or + `visual_blocks`. """ - visual_blocks: Iterable[VisualBlock] - """The visual blocks of the email. - - Either `html_body` or `visual_blocks` must be provided. - """ + visual_blocks: Optional[Iterable[VisualBlock]] + """The visual blocks that make up the email template.""" diff --git a/src/knock_mapi/types/environment.py b/src/knock_mapi/types/environment.py index 0fac90e4..5646e4b9 100644 --- a/src/knock_mapi/types/environment.py +++ b/src/knock_mapi/types/environment.py @@ -10,6 +10,8 @@ class Environment(BaseModel): + """An environment object.""" + created_at: datetime """The timestamp of when the environment was created.""" diff --git a/src/knock_mapi/types/guide.py b/src/knock_mapi/types/guide.py new file mode 100644 index 00000000..19249264 --- /dev/null +++ b/src/knock_mapi/types/guide.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from .._models import BaseModel +from .guide_step import GuideStep +from .condition_group import ConditionGroup +from .guide_activation_url_pattern import GuideActivationURLPattern + +__all__ = ["Guide"] + + +class Guide(BaseModel): + """ + A guide defines an in-app guide that can be displayed to users based on priority and other conditions. + """ + + active: bool + """Whether the guide is active.""" + + created_at: datetime + """The timestamp of when the guide was created.""" + + environment: str + """The slug of the environment in which the guide exists.""" + + key: str + """The unique key string for the guide object. + + Must be at minimum 3 characters and at maximum 255 characters in length. Must be + in the format of ^[a-z0-9_-]+$. + """ + + name: str + """A name for the guide. Must be at maximum 255 characters in length.""" + + sha: str + """The SHA hash of the guide.""" + + updated_at: datetime + """The timestamp of when the guide was last updated.""" + + activation_url_patterns: Optional[List[GuideActivationURLPattern]] = None + """A list of activation url patterns that describe when the guide should be shown.""" + + archived_at: Optional[datetime] = None + """The timestamp of when the guide was archived.""" + + channel_key: Optional[str] = None + """The key of the channel in which the guide exists.""" + + deleted_at: Optional[datetime] = None + """The timestamp of when the guide was deleted.""" + + description: Optional[str] = None + """An arbitrary string attached to a guide object. + + Useful for adding notes about the guide for internal purposes. Maximum of 280 + characters allowed. + """ + + semver: Optional[str] = None + """The semver of the guide.""" + + steps: Optional[List[GuideStep]] = None + """A list of guide step objects in the guide.""" + + target_audience_id: Optional[str] = None + """The ID of the target audience for the guide. + + When not set, will default to targeting all users. + """ + + target_property_conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + type: Optional[str] = None + """The type of the guide. + + This is derived from the message type of the guide steps. + """ + + valid: Optional[bool] = None + """Whether the guide is valid.""" diff --git a/src/knock_mapi/types/guide_activate_params.py b/src/knock_mapi/types/guide_activate_params.py new file mode 100644 index 00000000..e1692ec6 --- /dev/null +++ b/src/knock_mapi/types/guide_activate_params.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypeAlias, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["GuideActivateParams", "GuideBooleanActivationParams", "GuideScheduledActivationParams"] + + +class GuideBooleanActivationParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + status: Required[bool] + """Whether to activate or deactivate the guide.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +class GuideScheduledActivationParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + from_: Annotated[Union[str, datetime], PropertyInfo(alias="from", format="iso8601")] + """When to activate the guide. + + If provided, the guide will be scheduled to activate at this time. Must be in + ISO 8601 UTC format. + """ + + until: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """When to deactivate the guide. + + If provided, the guide will be scheduled to deactivate at this time. Must be in + ISO 8601 UTC format. + """ + + +GuideActivateParams: TypeAlias = Union[GuideBooleanActivationParams, GuideScheduledActivationParams] diff --git a/src/knock_mapi/types/guide_activate_response.py b/src/knock_mapi/types/guide_activate_response.py new file mode 100644 index 00000000..fbf25fcc --- /dev/null +++ b/src/knock_mapi/types/guide_activate_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .guide import Guide +from .._models import BaseModel + +__all__ = ["GuideActivateResponse"] + + +class GuideActivateResponse(BaseModel): + """Wraps the Guide response under the `guide` key.""" + + guide: Guide + """ + A guide defines an in-app guide that can be displayed to users based on priority + and other conditions. + """ diff --git a/src/knock_mapi/types/guide_activation_url_pattern.py b/src/knock_mapi/types/guide_activation_url_pattern.py new file mode 100644 index 00000000..53834830 --- /dev/null +++ b/src/knock_mapi/types/guide_activation_url_pattern.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["GuideActivationURLPattern"] + + +class GuideActivationURLPattern(BaseModel): + """ + A rule that controls when a guide should be shown based on the user's location in the application. At least one of `pathname` or `search` must be provided. + """ + + directive: Literal["allow", "block"] + """Whether to allow or block the guide at the specified location.""" + + pathname: Optional[str] = None + """The URL pathname pattern to match against. Must be a valid URI path.""" + + search: Optional[str] = None + """The URL query string pattern to match against (without the leading '?'). + + Supports URLPattern API syntax. + """ diff --git a/src/knock_mapi/types/guide_activation_url_pattern_param.py b/src/knock_mapi/types/guide_activation_url_pattern_param.py new file mode 100644 index 00000000..7ea0ed2d --- /dev/null +++ b/src/knock_mapi/types/guide_activation_url_pattern_param.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["GuideActivationURLPatternParam"] + + +class GuideActivationURLPatternParam(TypedDict, total=False): + """ + A rule that controls when a guide should be shown based on the user's location in the application. At least one of `pathname` or `search` must be provided. + """ + + directive: Required[Literal["allow", "block"]] + """Whether to allow or block the guide at the specified location.""" + + pathname: str + """The URL pathname pattern to match against. Must be a valid URI path.""" + + search: str + """The URL query string pattern to match against (without the leading '?'). + + Supports URLPattern API syntax. + """ diff --git a/src/knock_mapi/types/guide_archive_response.py b/src/knock_mapi/types/guide_archive_response.py new file mode 100644 index 00000000..e930e199 --- /dev/null +++ b/src/knock_mapi/types/guide_archive_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["GuideArchiveResponse"] + + +class GuideArchiveResponse(BaseModel): + """The response from archiving a guide.""" + + result: str + """The result of the promote operation.""" diff --git a/src/knock_mapi/types/guide_list_params.py b/src/knock_mapi/types/guide_list_params.py new file mode 100644 index 00000000..0f49fe41 --- /dev/null +++ b/src/knock_mapi/types/guide_list_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["GuideListParams"] + + +class GuideListParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + after: str + """The cursor to fetch entries after.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + before: str + """The cursor to fetch entries before.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ + + limit: int + """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/guide_retrieve_params.py b/src/knock_mapi/types/guide_retrieve_params.py new file mode 100644 index 00000000..4f98219f --- /dev/null +++ b/src/knock_mapi/types/guide_retrieve_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["GuideRetrieveParams"] + + +class GuideRetrieveParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + hide_uncommitted_changes: bool + """Whether to hide uncommitted changes. + + When true, only committed changes will be returned. When false, both committed + and uncommitted changes will be returned. + """ diff --git a/src/knock_mapi/types/guide_step.py b/src/knock_mapi/types/guide_step.py new file mode 100644 index 00000000..6143cb4f --- /dev/null +++ b/src/knock_mapi/types/guide_step.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from .._models import BaseModel + +__all__ = ["GuideStep"] + + +class GuideStep(BaseModel): + """A step in a guide that corresponds to a piece of UI and its content.""" + + ref: str + """The unique reference string for the step. + + Must be at minimum 3 characters and at maximum 255 characters in length. Must be + in the format of ^[a-z0-9_-]+$. + """ + + schema_key: str + """The key of the template schema to use for this step.""" + + schema_semver: str + """The semantic version of the template schema to use.""" + + schema_variant_key: str + """The key of the template schema variant to use.""" + + name: Optional[str] = None + """A name for the step.""" + + values: Optional[Dict[str, object]] = None + """A map of values that make up the step's content. + + Each value must conform to its respective template schema field settings. + """ diff --git a/src/knock_mapi/types/guide_step_param.py b/src/knock_mapi/types/guide_step_param.py new file mode 100644 index 00000000..e202de99 --- /dev/null +++ b/src/knock_mapi/types/guide_step_param.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import Required, TypedDict + +__all__ = ["GuideStepParam"] + + +class GuideStepParam(TypedDict, total=False): + """A step in a guide that corresponds to a piece of UI and its content.""" + + ref: Required[str] + """The unique reference string for the step. + + Must be at minimum 3 characters and at maximum 255 characters in length. Must be + in the format of ^[a-z0-9_-]+$. + """ + + schema_key: Required[str] + """The key of the template schema to use for this step.""" + + schema_semver: Required[str] + """The semantic version of the template schema to use.""" + + schema_variant_key: Required[str] + """The key of the template schema variant to use.""" + + name: str + """A name for the step.""" + + values: Dict[str, object] + """A map of values that make up the step's content. + + Each value must conform to its respective template schema field settings. + """ diff --git a/src/knock_mapi/types/guide_upsert_params.py b/src/knock_mapi/types/guide_upsert_params.py new file mode 100644 index 00000000..fe79da04 --- /dev/null +++ b/src/knock_mapi/types/guide_upsert_params.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .guide_step_param import GuideStepParam +from .condition_group_param import ConditionGroupParam +from .guide_activation_url_pattern_param import GuideActivationURLPatternParam + +__all__ = ["GuideUpsertParams", "Guide"] + + +class GuideUpsertParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + guide: Required[Guide] + """A request to create or update a guide.""" + + annotate: bool + """Whether to annotate the resource. Only used in the Knock CLI.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + commit: bool + """Whether to commit the resource at the same time as modifying it.""" + + commit_message: str + """The message to commit the resource with, only used if `commit` is `true`.""" + + +class Guide(TypedDict, total=False): + """A request to create or update a guide.""" + + channel_key: Required[str] + """The key of the channel in which the guide exists.""" + + name: Required[str] + """A name for the guide. Must be at maximum 255 characters in length.""" + + steps: Required[Iterable[GuideStepParam]] + """A list of guide step objects in the guide.""" + + activation_url_patterns: Iterable[GuideActivationURLPatternParam] + """A list of activation url patterns that describe when the guide should be shown.""" + + archived_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The timestamp of when the guide was archived.""" + + deleted_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The timestamp of when the guide was deleted.""" + + description: Optional[str] + """An arbitrary string attached to a guide object. + + Useful for adding notes about the guide for internal purposes. Maximum of 280 + characters allowed. + """ + + target_audience_id: Optional[str] + """The ID of the target audience for the guide. + + When not set, will default to targeting all users. + """ + + target_property_conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" diff --git a/src/knock_mapi/types/guide_upsert_response.py b/src/knock_mapi/types/guide_upsert_response.py new file mode 100644 index 00000000..8c2957b8 --- /dev/null +++ b/src/knock_mapi/types/guide_upsert_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .guide import Guide +from .._models import BaseModel + +__all__ = ["GuideUpsertResponse"] + + +class GuideUpsertResponse(BaseModel): + """Wraps the Guide response under the `guide` key.""" + + guide: Guide + """ + A guide defines an in-app guide that can be displayed to users based on priority + and other conditions. + """ diff --git a/src/knock_mapi/types/guide_validate_params.py b/src/knock_mapi/types/guide_validate_params.py new file mode 100644 index 00000000..77b2ab87 --- /dev/null +++ b/src/knock_mapi/types/guide_validate_params.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .guide_step_param import GuideStepParam +from .condition_group_param import ConditionGroupParam +from .guide_activation_url_pattern_param import GuideActivationURLPatternParam + +__all__ = ["GuideValidateParams", "Guide"] + + +class GuideValidateParams(TypedDict, total=False): + environment: Required[str] + """The environment slug.""" + + guide: Required[Guide] + """A request to create or update a guide.""" + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +class Guide(TypedDict, total=False): + """A request to create or update a guide.""" + + channel_key: Required[str] + """The key of the channel in which the guide exists.""" + + name: Required[str] + """A name for the guide. Must be at maximum 255 characters in length.""" + + steps: Required[Iterable[GuideStepParam]] + """A list of guide step objects in the guide.""" + + activation_url_patterns: Iterable[GuideActivationURLPatternParam] + """A list of activation url patterns that describe when the guide should be shown.""" + + archived_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The timestamp of when the guide was archived.""" + + deleted_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The timestamp of when the guide was deleted.""" + + description: Optional[str] + """An arbitrary string attached to a guide object. + + Useful for adding notes about the guide for internal purposes. Maximum of 280 + characters allowed. + """ + + target_audience_id: Optional[str] + """The ID of the target audience for the guide. + + When not set, will default to targeting all users. + """ + + target_property_conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" diff --git a/src/knock_mapi/types/guide_validate_response.py b/src/knock_mapi/types/guide_validate_response.py new file mode 100644 index 00000000..0ee351aa --- /dev/null +++ b/src/knock_mapi/types/guide_validate_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .guide import Guide +from .._models import BaseModel + +__all__ = ["GuideValidateResponse"] + + +class GuideValidateResponse(BaseModel): + """Wraps the Guide response under the `guide` key.""" + + guide: Guide + """ + A guide defines an in-app guide that can be displayed to users based on priority + and other conditions. + """ diff --git a/src/knock_mapi/types/in_app_feed_channel_settings.py b/src/knock_mapi/types/in_app_feed_channel_settings.py index a6c99238..21022b21 100644 --- a/src/knock_mapi/types/in_app_feed_channel_settings.py +++ b/src/knock_mapi/types/in_app_feed_channel_settings.py @@ -8,5 +8,10 @@ class InAppFeedChannelSettings(BaseModel): + """In-app feed channel settings. + + Only used as configuration as part of a workflow channel step. + """ + link_tracking: Optional[bool] = None """Whether to track link clicks on in-app feed notifications.""" diff --git a/src/knock_mapi/types/in_app_feed_channel_settings_param.py b/src/knock_mapi/types/in_app_feed_channel_settings_param.py index f674cbcd..0a300f06 100644 --- a/src/knock_mapi/types/in_app_feed_channel_settings_param.py +++ b/src/knock_mapi/types/in_app_feed_channel_settings_param.py @@ -8,5 +8,10 @@ class InAppFeedChannelSettingsParam(TypedDict, total=False): + """In-app feed channel settings. + + Only used as configuration as part of a workflow channel step. + """ + link_tracking: bool """Whether to track link clicks on in-app feed notifications.""" diff --git a/src/knock_mapi/types/in_app_feed_template.py b/src/knock_mapi/types/in_app_feed_template.py index 55a23de0..94b6069d 100644 --- a/src/knock_mapi/types/in_app_feed_template.py +++ b/src/knock_mapi/types/in_app_feed_template.py @@ -8,6 +8,8 @@ class ActionButton(BaseModel): + """A single-action button to be rendered in an in-app feed cell.""" + action: str """The URI for this action.""" @@ -16,6 +18,8 @@ class ActionButton(BaseModel): class InAppFeedTemplate(BaseModel): + """An in-app feed template.""" + markdown_body: str """The markdown body of the in-app feed.""" diff --git a/src/knock_mapi/types/in_app_feed_template_param.py b/src/knock_mapi/types/in_app_feed_template_param.py index bf017f1b..6ec2fd19 100644 --- a/src/knock_mapi/types/in_app_feed_template_param.py +++ b/src/knock_mapi/types/in_app_feed_template_param.py @@ -9,6 +9,8 @@ class ActionButton(TypedDict, total=False): + """A single-action button to be rendered in an in-app feed cell.""" + action: Required[str] """The URI for this action.""" @@ -17,6 +19,8 @@ class ActionButton(TypedDict, total=False): class InAppFeedTemplateParam(TypedDict, total=False): + """An in-app feed template.""" + markdown_body: Required[str] """The markdown body of the in-app feed.""" diff --git a/src/knock_mapi/types/member.py b/src/knock_mapi/types/member.py new file mode 100644 index 00000000..7d96065e --- /dev/null +++ b/src/knock_mapi/types/member.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .member_user import MemberUser + +__all__ = ["Member"] + + +class Member(BaseModel): + """A member of the account.""" + + id: str + """The unique identifier of the member.""" + + created_at: datetime + """The timestamp of when the member joined the account.""" + + role: Literal["owner", "admin", "member", "production_only_member", "billing", "support"] + """The member's role in the account.""" + + updated_at: datetime + """The timestamp of when the member was last updated.""" + + user: MemberUser + """Information about a user within the Knock dashboard. + + Not to be confused with an external user (recipient) of a workflow. + """ diff --git a/src/knock_mapi/types/member_list_params.py b/src/knock_mapi/types/member_list_params.py new file mode 100644 index 00000000..95b438ff --- /dev/null +++ b/src/knock_mapi/types/member_list_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["MemberListParams"] + + +class MemberListParams(TypedDict, total=False): + after: str + """The cursor to fetch entries after.""" + + before: str + """The cursor to fetch entries before.""" + + email: str + """Filter members by email address (exact match).""" + + limit: int + """The number of entries to fetch per-page.""" + + role: str + """Filter members by role. + + One of: owner, admin, member, production_only_member, billing, support. + """ diff --git a/src/knock_mapi/types/member_user.py b/src/knock_mapi/types/member_user.py new file mode 100644 index 00000000..21f2c277 --- /dev/null +++ b/src/knock_mapi/types/member_user.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["MemberUser"] + + +class MemberUser(BaseModel): + """Information about a user within the Knock dashboard. + + Not to be confused with an external user (recipient) of a workflow. + """ + + id: str + """The user's unique identifier.""" + + created_at: datetime + """The timestamp of when the user was created.""" + + email: str + """The user's email address.""" + + updated_at: datetime + """The timestamp of when the user was last updated.""" + + avatar_url: Optional[str] = None + """The URL of the user's avatar image.""" + + name: Optional[str] = None + """The user's display name.""" diff --git a/src/knock_mapi/types/message_type.py b/src/knock_mapi/types/message_type.py index 05dd65be..538aae8b 100644 --- a/src/knock_mapi/types/message_type.py +++ b/src/knock_mapi/types/message_type.py @@ -11,6 +11,10 @@ class MessageType(BaseModel): + """ + A message type is a schema for a message that maps to a UI component or element within your application. + """ + created_at: datetime """The timestamp of when the message type was created.""" diff --git a/src/knock_mapi/types/message_type_list_params.py b/src/knock_mapi/types/message_type_list_params.py index 7207dbe7..4f413233 100644 --- a/src/knock_mapi/types/message_type_list_params.py +++ b/src/knock_mapi/types/message_type_list_params.py @@ -20,6 +20,12 @@ class MessageTypeListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/message_type_retrieve_params.py b/src/knock_mapi/types/message_type_retrieve_params.py index 426be466..af59e6e2 100644 --- a/src/knock_mapi/types/message_type_retrieve_params.py +++ b/src/knock_mapi/types/message_type_retrieve_params.py @@ -14,6 +14,12 @@ class MessageTypeRetrieveParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/message_type_text_field.py b/src/knock_mapi/types/message_type_text_field.py index 4ff018e9..4f10ac47 100644 --- a/src/knock_mapi/types/message_type_text_field.py +++ b/src/knock_mapi/types/message_type_text_field.py @@ -9,6 +9,8 @@ class Settings(BaseModel): + """Settings for the text field.""" + default: Optional[str] = None """The default value of the text field.""" @@ -18,11 +20,15 @@ class Settings(BaseModel): min_length: Optional[int] = None + placeholder: Optional[str] = None + required: Optional[bool] = None """Whether the field is required.""" class MessageTypeTextField(BaseModel): + """A text field used in a message type.""" + key: str """The unique key of the field.""" diff --git a/src/knock_mapi/types/message_type_text_field_param.py b/src/knock_mapi/types/message_type_text_field_param.py index 25fd777d..adcbaf4f 100644 --- a/src/knock_mapi/types/message_type_text_field_param.py +++ b/src/knock_mapi/types/message_type_text_field_param.py @@ -9,20 +9,26 @@ class Settings(TypedDict, total=False): + """Settings for the text field.""" + default: Optional[str] """The default value of the text field.""" - description: str + description: Optional[str] max_length: int min_length: int + placeholder: Optional[str] + required: bool """Whether the field is required.""" class MessageTypeTextFieldParam(TypedDict, total=False): + """A text field used in a message type.""" + key: Required[str] """The unique key of the field.""" diff --git a/src/knock_mapi/types/message_type_upsert_params.py b/src/knock_mapi/types/message_type_upsert_params.py index e8d98a5b..3b3fc078 100644 --- a/src/knock_mapi/types/message_type_upsert_params.py +++ b/src/knock_mapi/types/message_type_upsert_params.py @@ -20,6 +20,12 @@ class MessageTypeUpsertParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit: bool """Whether to commit the resource at the same time as modifying it.""" @@ -28,6 +34,8 @@ class MessageTypeUpsertParams(TypedDict, total=False): class MessageType(TypedDict, total=False): + """A request to create a message type.""" + description: Required[Optional[str]] """An arbitrary string attached to a message type object. diff --git a/src/knock_mapi/types/message_type_upsert_response.py b/src/knock_mapi/types/message_type_upsert_response.py index 3c775056..d93e1dcd 100644 --- a/src/knock_mapi/types/message_type_upsert_response.py +++ b/src/knock_mapi/types/message_type_upsert_response.py @@ -7,6 +7,8 @@ class MessageTypeUpsertResponse(BaseModel): + """Wraps the MessageType response under the `message_type` key.""" + message_type: MessageType """ A message type is a schema for a message that maps to a UI component or element diff --git a/src/knock_mapi/types/message_type_validate_params.py b/src/knock_mapi/types/message_type_validate_params.py index 4b14a625..6574f8d4 100644 --- a/src/knock_mapi/types/message_type_validate_params.py +++ b/src/knock_mapi/types/message_type_validate_params.py @@ -17,8 +17,16 @@ class MessageTypeValidateParams(TypedDict, total=False): message_type: Required[MessageType] """A request to create a message type.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + class MessageType(TypedDict, total=False): + """A request to create a message type.""" + description: Required[Optional[str]] """An arbitrary string attached to a message type object. diff --git a/src/knock_mapi/types/message_type_validate_response.py b/src/knock_mapi/types/message_type_validate_response.py index d1ff302c..a7f5f39e 100644 --- a/src/knock_mapi/types/message_type_validate_response.py +++ b/src/knock_mapi/types/message_type_validate_response.py @@ -7,6 +7,8 @@ class MessageTypeValidateResponse(BaseModel): + """Wraps the MessageType response under the `message_type` key.""" + message_type: MessageType """ A message type is a schema for a message that maps to a UI component or element diff --git a/src/knock_mapi/types/message_type_variant.py b/src/knock_mapi/types/message_type_variant.py index 2c69539b..b505b0b1 100644 --- a/src/knock_mapi/types/message_type_variant.py +++ b/src/knock_mapi/types/message_type_variant.py @@ -1,302 +1,39 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional -from typing_extensions import Literal, TypeAlias +from typing import List, Union +from typing_extensions import TypeAlias from .._models import BaseModel from .message_type_text_field import MessageTypeTextField - -__all__ = [ - "MessageTypeVariant", - "Field", - "FieldMessageTypeBooleanField", - "FieldMessageTypeBooleanFieldSettings", - "FieldMessageTypeButtonField", - "FieldMessageTypeButtonFieldSettings", - "FieldMessageTypeImageField", - "FieldMessageTypeImageFieldURL", - "FieldMessageTypeImageFieldURLSettings", - "FieldMessageTypeImageFieldSettings", - "FieldMessageTypeMarkdownField", - "FieldMessageTypeMarkdownFieldSettings", - "FieldMessageTypeMultiSelectField", - "FieldMessageTypeMultiSelectFieldSettings", - "FieldMessageTypeMultiSelectFieldSettingsOption", - "FieldMessageTypeSelectField", - "FieldMessageTypeSelectFieldSettings", - "FieldMessageTypeSelectFieldSettingsOption", - "FieldMessageTypeTextareaField", - "FieldMessageTypeTextareaFieldSettings", - "FieldMessageTypeURLField", - "FieldMessageTypeURLFieldSettings", -] - - -class FieldMessageTypeBooleanFieldSettings(BaseModel): - default: Optional[bool] = None - """The default value of the boolean field.""" - - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeBooleanField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["boolean"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeBooleanFieldSettings] = None - """Settings for the boolean field.""" - - -class FieldMessageTypeButtonFieldSettings(BaseModel): - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeButtonField(BaseModel): - action: MessageTypeTextField - """A text field used in a message type.""" - - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - text: MessageTypeTextField - """A text field used in a message type.""" - - type: Literal["button"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeButtonFieldSettings] = None - """Settings for the button field.""" - - -class FieldMessageTypeImageFieldURLSettings(BaseModel): - default: Optional[str] = None - """The default value of the URL field.""" - - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeImageFieldURL(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["url"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeImageFieldURLSettings] = None - """Settings for the url field.""" - - -class FieldMessageTypeImageFieldSettings(BaseModel): - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeImageField(BaseModel): - action: MessageTypeTextField - """A text field used in a message type.""" - - alt: MessageTypeTextField - """A text field used in a message type.""" - - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["image"] - """The type of the field.""" - - url: FieldMessageTypeImageFieldURL - """A URL field used in a message type.""" - - settings: Optional[FieldMessageTypeImageFieldSettings] = None - """Settings for the image field.""" - - -class FieldMessageTypeMarkdownFieldSettings(BaseModel): - default: Optional[str] = None - """The default value of the markdown field.""" - - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeMarkdownField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["markdown"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeMarkdownFieldSettings] = None - """Settings for the markdown field.""" - - -class FieldMessageTypeMultiSelectFieldSettingsOption(BaseModel): - value: str - """The value for the option.""" - - label: Optional[str] = None - """The display label for the option.""" - - -class FieldMessageTypeMultiSelectFieldSettings(BaseModel): - default: Optional[List[str]] = None - """The default values for the multi-select field.""" - - description: Optional[str] = None - - options: Optional[List[FieldMessageTypeMultiSelectFieldSettingsOption]] = None - """The available options for the multi-select field.""" - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeMultiSelectField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - settings: FieldMessageTypeMultiSelectFieldSettings - """Settings for the multi_select field.""" - - type: Literal["multi_select"] - """The type of the field.""" - - -class FieldMessageTypeSelectFieldSettingsOption(BaseModel): - value: str - """The value for the option.""" - - label: Optional[str] = None - """The display label for the option.""" - - -class FieldMessageTypeSelectFieldSettings(BaseModel): - default: Optional[str] = None - """The default value for the select field.""" - - description: Optional[str] = None - - options: Optional[List[FieldMessageTypeSelectFieldSettingsOption]] = None - """The available options for the select field.""" - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeSelectField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - settings: FieldMessageTypeSelectFieldSettings - """Settings for the select field.""" - - type: Literal["select"] - """The type of the field.""" - - -class FieldMessageTypeTextareaFieldSettings(BaseModel): - default: Optional[str] = None - """The default value of the textarea field.""" - - description: Optional[str] = None - - max_length: Optional[int] = None - - min_length: Optional[int] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeTextareaField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["textarea"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeTextareaFieldSettings] = None - """Settings for the textarea field.""" - - -class FieldMessageTypeURLFieldSettings(BaseModel): - default: Optional[str] = None - """The default value of the URL field.""" - - description: Optional[str] = None - - required: Optional[bool] = None - """Whether the field is required.""" - - -class FieldMessageTypeURLField(BaseModel): - key: str - """The unique key of the field.""" - - label: Optional[str] = None - """The label of the field.""" - - type: Literal["url"] - """The type of the field.""" - - settings: Optional[FieldMessageTypeURLFieldSettings] = None - """Settings for the url field.""" - +from .shared.message_type_url_field import MessageTypeURLField +from .shared.message_type_json_field import MessageTypeJsonField +from .shared.message_type_image_field import MessageTypeImageField +from .shared.message_type_button_field import MessageTypeButtonField +from .shared.message_type_select_field import MessageTypeSelectField +from .shared.message_type_boolean_field import MessageTypeBooleanField +from .shared.message_type_markdown_field import MessageTypeMarkdownField +from .shared.message_type_textarea_field import MessageTypeTextareaField +from .shared.message_type_multi_select_field import MessageTypeMultiSelectField + +__all__ = ["MessageTypeVariant", "Field"] Field: TypeAlias = Union[ - FieldMessageTypeBooleanField, - FieldMessageTypeButtonField, - FieldMessageTypeImageField, - FieldMessageTypeMarkdownField, - FieldMessageTypeMultiSelectField, - FieldMessageTypeSelectField, + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, MessageTypeTextField, - FieldMessageTypeTextareaField, - FieldMessageTypeURLField, + MessageTypeTextareaField, + MessageTypeURLField, ] class MessageTypeVariant(BaseModel): + """A variant of a message type.""" + fields: List[Field] """The field types available for the variant.""" diff --git a/src/knock_mapi/types/message_type_variant_param.py b/src/knock_mapi/types/message_type_variant_param.py index 17890813..988203a8 100644 --- a/src/knock_mapi/types/message_type_variant_param.py +++ b/src/knock_mapi/types/message_type_variant_param.py @@ -2,302 +2,39 @@ from __future__ import annotations -from typing import List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import Union, Iterable +from typing_extensions import Required, TypeAlias, TypedDict from .message_type_text_field_param import MessageTypeTextFieldParam - -__all__ = [ - "MessageTypeVariantParam", - "Field", - "FieldMessageTypeBooleanField", - "FieldMessageTypeBooleanFieldSettings", - "FieldMessageTypeButtonField", - "FieldMessageTypeButtonFieldSettings", - "FieldMessageTypeImageField", - "FieldMessageTypeImageFieldURL", - "FieldMessageTypeImageFieldURLSettings", - "FieldMessageTypeImageFieldSettings", - "FieldMessageTypeMarkdownField", - "FieldMessageTypeMarkdownFieldSettings", - "FieldMessageTypeMultiSelectField", - "FieldMessageTypeMultiSelectFieldSettings", - "FieldMessageTypeMultiSelectFieldSettingsOption", - "FieldMessageTypeSelectField", - "FieldMessageTypeSelectFieldSettings", - "FieldMessageTypeSelectFieldSettingsOption", - "FieldMessageTypeTextareaField", - "FieldMessageTypeTextareaFieldSettings", - "FieldMessageTypeURLField", - "FieldMessageTypeURLFieldSettings", -] - - -class FieldMessageTypeBooleanFieldSettings(TypedDict, total=False): - default: bool - """The default value of the boolean field.""" - - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeBooleanField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["boolean"]] - """The type of the field.""" - - settings: FieldMessageTypeBooleanFieldSettings - """Settings for the boolean field.""" - - -class FieldMessageTypeButtonFieldSettings(TypedDict, total=False): - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeButtonField(TypedDict, total=False): - action: Required[MessageTypeTextFieldParam] - """A text field used in a message type.""" - - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - text: Required[MessageTypeTextFieldParam] - """A text field used in a message type.""" - - type: Required[Literal["button"]] - """The type of the field.""" - - settings: FieldMessageTypeButtonFieldSettings - """Settings for the button field.""" - - -class FieldMessageTypeImageFieldURLSettings(TypedDict, total=False): - default: Optional[str] - """The default value of the URL field.""" - - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeImageFieldURL(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["url"]] - """The type of the field.""" - - settings: FieldMessageTypeImageFieldURLSettings - """Settings for the url field.""" - - -class FieldMessageTypeImageFieldSettings(TypedDict, total=False): - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeImageField(TypedDict, total=False): - action: Required[MessageTypeTextFieldParam] - """A text field used in a message type.""" - - alt: Required[MessageTypeTextFieldParam] - """A text field used in a message type.""" - - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["image"]] - """The type of the field.""" - - url: Required[FieldMessageTypeImageFieldURL] - """A URL field used in a message type.""" - - settings: FieldMessageTypeImageFieldSettings - """Settings for the image field.""" - - -class FieldMessageTypeMarkdownFieldSettings(TypedDict, total=False): - default: str - """The default value of the markdown field.""" - - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeMarkdownField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["markdown"]] - """The type of the field.""" - - settings: FieldMessageTypeMarkdownFieldSettings - """Settings for the markdown field.""" - - -class FieldMessageTypeMultiSelectFieldSettingsOption(TypedDict, total=False): - value: Required[str] - """The value for the option.""" - - label: str - """The display label for the option.""" - - -class FieldMessageTypeMultiSelectFieldSettings(TypedDict, total=False): - default: Optional[List[str]] - """The default values for the multi-select field.""" - - description: str - - options: Iterable[FieldMessageTypeMultiSelectFieldSettingsOption] - """The available options for the multi-select field.""" - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeMultiSelectField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - settings: Required[FieldMessageTypeMultiSelectFieldSettings] - """Settings for the multi_select field.""" - - type: Required[Literal["multi_select"]] - """The type of the field.""" - - -class FieldMessageTypeSelectFieldSettingsOption(TypedDict, total=False): - value: Required[str] - """The value for the option.""" - - label: str - """The display label for the option.""" - - -class FieldMessageTypeSelectFieldSettings(TypedDict, total=False): - default: Optional[str] - """The default value for the select field.""" - - description: str - - options: Iterable[FieldMessageTypeSelectFieldSettingsOption] - """The available options for the select field.""" - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeSelectField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - settings: Required[FieldMessageTypeSelectFieldSettings] - """Settings for the select field.""" - - type: Required[Literal["select"]] - """The type of the field.""" - - -class FieldMessageTypeTextareaFieldSettings(TypedDict, total=False): - default: Optional[str] - """The default value of the textarea field.""" - - description: str - - max_length: int - - min_length: int - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeTextareaField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["textarea"]] - """The type of the field.""" - - settings: FieldMessageTypeTextareaFieldSettings - """Settings for the textarea field.""" - - -class FieldMessageTypeURLFieldSettings(TypedDict, total=False): - default: Optional[str] - """The default value of the URL field.""" - - description: str - - required: bool - """Whether the field is required.""" - - -class FieldMessageTypeURLField(TypedDict, total=False): - key: Required[str] - """The unique key of the field.""" - - label: Required[Optional[str]] - """The label of the field.""" - - type: Required[Literal["url"]] - """The type of the field.""" - - settings: FieldMessageTypeURLFieldSettings - """Settings for the url field.""" - +from .shared_params.message_type_url_field import MessageTypeURLField +from .shared_params.message_type_json_field import MessageTypeJsonField +from .shared_params.message_type_image_field import MessageTypeImageField +from .shared_params.message_type_button_field import MessageTypeButtonField +from .shared_params.message_type_select_field import MessageTypeSelectField +from .shared_params.message_type_boolean_field import MessageTypeBooleanField +from .shared_params.message_type_markdown_field import MessageTypeMarkdownField +from .shared_params.message_type_textarea_field import MessageTypeTextareaField +from .shared_params.message_type_multi_select_field import MessageTypeMultiSelectField + +__all__ = ["MessageTypeVariantParam", "Field"] Field: TypeAlias = Union[ - FieldMessageTypeBooleanField, - FieldMessageTypeButtonField, - FieldMessageTypeImageField, - FieldMessageTypeMarkdownField, - FieldMessageTypeMultiSelectField, - FieldMessageTypeSelectField, + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, MessageTypeTextFieldParam, - FieldMessageTypeTextareaField, - FieldMessageTypeURLField, + MessageTypeTextareaField, + MessageTypeURLField, ] class MessageTypeVariantParam(TypedDict, total=False): + """A variant of a message type.""" + fields: Required[Iterable[Field]] """The field types available for the variant.""" diff --git a/src/knock_mapi/types/partial.py b/src/knock_mapi/types/partial.py index 16e818fc..dbf6de0b 100644 --- a/src/knock_mapi/types/partial.py +++ b/src/knock_mapi/types/partial.py @@ -1,15 +1,40 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Union, Optional from datetime import datetime -from typing_extensions import Literal +from typing_extensions import Literal, TypeAlias from .._models import BaseModel - -__all__ = ["Partial"] +from .message_type_text_field import MessageTypeTextField +from .shared.message_type_url_field import MessageTypeURLField +from .shared.message_type_json_field import MessageTypeJsonField +from .shared.message_type_image_field import MessageTypeImageField +from .shared.message_type_button_field import MessageTypeButtonField +from .shared.message_type_select_field import MessageTypeSelectField +from .shared.message_type_boolean_field import MessageTypeBooleanField +from .shared.message_type_markdown_field import MessageTypeMarkdownField +from .shared.message_type_textarea_field import MessageTypeTextareaField +from .shared.message_type_multi_select_field import MessageTypeMultiSelectField + +__all__ = ["Partial", "InputSchema"] + +InputSchema: TypeAlias = Union[ + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, + MessageTypeTextField, + MessageTypeTextareaField, + MessageTypeURLField, +] class Partial(BaseModel): + """A partial is a reusable piece of content that can be used in a template.""" + content: str """The partial content.""" @@ -48,6 +73,9 @@ class Partial(BaseModel): icon_name: Optional[str] = None """The name of the icon to be used in the visual editor.""" + input_schema: Optional[List[InputSchema]] = None + """The field types available for the partial.""" + visual_block_enabled: Optional[bool] = None """Indicates whether the partial can be used in the visual editor. diff --git a/src/knock_mapi/types/partial_list_params.py b/src/knock_mapi/types/partial_list_params.py index 1c19a188..41d5a9c5 100644 --- a/src/knock_mapi/types/partial_list_params.py +++ b/src/knock_mapi/types/partial_list_params.py @@ -20,6 +20,12 @@ class PartialListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/partial_retrieve_params.py b/src/knock_mapi/types/partial_retrieve_params.py index 6b07e1e4..31a6b0df 100644 --- a/src/knock_mapi/types/partial_retrieve_params.py +++ b/src/knock_mapi/types/partial_retrieve_params.py @@ -14,6 +14,12 @@ class PartialRetrieveParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/partial_upsert_params.py b/src/knock_mapi/types/partial_upsert_params.py index a3c6609e..84efd2b7 100644 --- a/src/knock_mapi/types/partial_upsert_params.py +++ b/src/knock_mapi/types/partial_upsert_params.py @@ -2,10 +2,21 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict -__all__ = ["PartialUpsertParams", "Partial"] +from .message_type_text_field_param import MessageTypeTextFieldParam +from .shared_params.message_type_url_field import MessageTypeURLField +from .shared_params.message_type_json_field import MessageTypeJsonField +from .shared_params.message_type_image_field import MessageTypeImageField +from .shared_params.message_type_button_field import MessageTypeButtonField +from .shared_params.message_type_select_field import MessageTypeSelectField +from .shared_params.message_type_boolean_field import MessageTypeBooleanField +from .shared_params.message_type_markdown_field import MessageTypeMarkdownField +from .shared_params.message_type_textarea_field import MessageTypeTextareaField +from .shared_params.message_type_multi_select_field import MessageTypeMultiSelectField + +__all__ = ["PartialUpsertParams", "Partial", "PartialInputSchema"] class PartialUpsertParams(TypedDict, total=False): @@ -18,6 +29,12 @@ class PartialUpsertParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit: bool """Whether to commit the resource at the same time as modifying it.""" @@ -25,26 +42,46 @@ class PartialUpsertParams(TypedDict, total=False): """The message to commit the resource with, only used if `commit` is `true`.""" +PartialInputSchema: TypeAlias = Union[ + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, + MessageTypeTextFieldParam, + MessageTypeTextareaField, + MessageTypeURLField, +] + + class Partial(TypedDict, total=False): + """A partial object with attributes to update or create a partial.""" + content: Required[str] - """The content of the partial.""" + """The partial content.""" name: Required[str] - """The name of the partial.""" + """A name for the partial. Must be at maximum 255 characters in length.""" type: Required[Literal["html", "text", "json", "markdown"]] - """The type of the partial.""" - - description: Optional[str] - """The description of the partial.""" + """The partial type. One of 'html', 'json', 'markdown', 'text'.""" - icon_name: Optional[str] - """The name of the icon to be used in the visual editor. + description: str + """An arbitrary string attached to a partial object. - Only relevant when `visual_block_enabled` is `true`. + Useful for adding notes about the partial for internal purposes. Maximum of 280 + characters allowed. """ - visual_block_enabled: Optional[bool] + icon_name: str + """The name of the icon to be used in the visual editor.""" + + input_schema: Iterable[PartialInputSchema] + """The field types available for the partial.""" + + visual_block_enabled: bool """Indicates whether the partial can be used in the visual editor. Only applies to HTML partials. diff --git a/src/knock_mapi/types/partial_upsert_response.py b/src/knock_mapi/types/partial_upsert_response.py index 72fa2b26..93ef6fcb 100644 --- a/src/knock_mapi/types/partial_upsert_response.py +++ b/src/knock_mapi/types/partial_upsert_response.py @@ -7,5 +7,7 @@ class PartialUpsertResponse(BaseModel): + """Wraps the Partial response under the `partial` key.""" + partial: Partial """A partial is a reusable piece of content that can be used in a template.""" diff --git a/src/knock_mapi/types/partial_validate_params.py b/src/knock_mapi/types/partial_validate_params.py index dde18a22..767aed90 100644 --- a/src/knock_mapi/types/partial_validate_params.py +++ b/src/knock_mapi/types/partial_validate_params.py @@ -2,10 +2,21 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict -__all__ = ["PartialValidateParams", "Partial"] +from .message_type_text_field_param import MessageTypeTextFieldParam +from .shared_params.message_type_url_field import MessageTypeURLField +from .shared_params.message_type_json_field import MessageTypeJsonField +from .shared_params.message_type_image_field import MessageTypeImageField +from .shared_params.message_type_button_field import MessageTypeButtonField +from .shared_params.message_type_select_field import MessageTypeSelectField +from .shared_params.message_type_boolean_field import MessageTypeBooleanField +from .shared_params.message_type_markdown_field import MessageTypeMarkdownField +from .shared_params.message_type_textarea_field import MessageTypeTextareaField +from .shared_params.message_type_multi_select_field import MessageTypeMultiSelectField + +__all__ = ["PartialValidateParams", "Partial", "PartialInputSchema"] class PartialValidateParams(TypedDict, total=False): @@ -15,27 +26,53 @@ class PartialValidateParams(TypedDict, total=False): partial: Required[Partial] """A partial object with attributes to update or create a partial.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + + +PartialInputSchema: TypeAlias = Union[ + MessageTypeBooleanField, + MessageTypeButtonField, + MessageTypeImageField, + MessageTypeJsonField, + MessageTypeMarkdownField, + MessageTypeMultiSelectField, + MessageTypeSelectField, + MessageTypeTextFieldParam, + MessageTypeTextareaField, + MessageTypeURLField, +] + class Partial(TypedDict, total=False): + """A partial object with attributes to update or create a partial.""" + content: Required[str] - """The content of the partial.""" + """The partial content.""" name: Required[str] - """The name of the partial.""" + """A name for the partial. Must be at maximum 255 characters in length.""" type: Required[Literal["html", "text", "json", "markdown"]] - """The type of the partial.""" - - description: Optional[str] - """The description of the partial.""" + """The partial type. One of 'html', 'json', 'markdown', 'text'.""" - icon_name: Optional[str] - """The name of the icon to be used in the visual editor. + description: str + """An arbitrary string attached to a partial object. - Only relevant when `visual_block_enabled` is `true`. + Useful for adding notes about the partial for internal purposes. Maximum of 280 + characters allowed. """ - visual_block_enabled: Optional[bool] + icon_name: str + """The name of the icon to be used in the visual editor.""" + + input_schema: Iterable[PartialInputSchema] + """The field types available for the partial.""" + + visual_block_enabled: bool """Indicates whether the partial can be used in the visual editor. Only applies to HTML partials. diff --git a/src/knock_mapi/types/partial_validate_response.py b/src/knock_mapi/types/partial_validate_response.py index 11cfea20..a738e336 100644 --- a/src/knock_mapi/types/partial_validate_response.py +++ b/src/knock_mapi/types/partial_validate_response.py @@ -7,5 +7,7 @@ class PartialValidateResponse(BaseModel): + """Wraps the Partial response under the `partial` key.""" + partial: Partial """A partial is a reusable piece of content that can be used in a template.""" diff --git a/src/knock_mapi/types/push_channel_settings.py b/src/knock_mapi/types/push_channel_settings.py index 0da4993b..7437aefb 100644 --- a/src/knock_mapi/types/push_channel_settings.py +++ b/src/knock_mapi/types/push_channel_settings.py @@ -8,6 +8,11 @@ class PushChannelSettings(BaseModel): + """Push channel settings. + + Only used as configuration as part of a workflow channel step. + """ + token_deregistration: Optional[bool] = None """Whether to deregister a push-token when a push send hard bounces. diff --git a/src/knock_mapi/types/push_channel_settings_param.py b/src/knock_mapi/types/push_channel_settings_param.py index 962713db..76451849 100644 --- a/src/knock_mapi/types/push_channel_settings_param.py +++ b/src/knock_mapi/types/push_channel_settings_param.py @@ -8,6 +8,11 @@ class PushChannelSettingsParam(TypedDict, total=False): + """Push channel settings. + + Only used as configuration as part of a workflow channel step. + """ + token_deregistration: bool """Whether to deregister a push-token when a push send hard bounces. diff --git a/src/knock_mapi/types/push_template.py b/src/knock_mapi/types/push_template.py index bee5c8cd..4fbb7842 100644 --- a/src/knock_mapi/types/push_template.py +++ b/src/knock_mapi/types/push_template.py @@ -9,11 +9,15 @@ class Settings(BaseModel): - delivery_type: Optional[Literal["silent", "content"]] = None + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) for the push template. + """ + + delivery_type: Literal["silent", "content"] """The delivery type of the push notification. - Defaults to `content`. Set as silent to send a data-only notification. When set - to `data`, no body will be sent. + Set as silent to send a data-only notification. When set to `silent`, no body + will be sent. """ payload_overrides: Optional[str] = None @@ -21,14 +25,16 @@ class Settings(BaseModel): class PushTemplate(BaseModel): + """A push notification template.""" + + settings: Settings + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) + for the push template. + """ + text_body: str """The body of the push notification.""" title: str """The title of the push notification.""" - - settings: Optional[Settings] = None - """ - The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) - for the push template. Can be omitted. - """ diff --git a/src/knock_mapi/types/push_template_param.py b/src/knock_mapi/types/push_template_param.py index a1e75da9..f1882fca 100644 --- a/src/knock_mapi/types/push_template_param.py +++ b/src/knock_mapi/types/push_template_param.py @@ -2,18 +2,21 @@ from __future__ import annotations -from typing import Optional from typing_extensions import Literal, Required, TypedDict __all__ = ["PushTemplateParam", "Settings"] class Settings(TypedDict, total=False): - delivery_type: Literal["silent", "content"] + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) for the push template. + """ + + delivery_type: Required[Literal["silent", "content"]] """The delivery type of the push notification. - Defaults to `content`. Set as silent to send a data-only notification. When set - to `data`, no body will be sent. + Set as silent to send a data-only notification. When set to `silent`, no body + will be sent. """ payload_overrides: str @@ -21,14 +24,16 @@ class Settings(TypedDict, total=False): class PushTemplateParam(TypedDict, total=False): + """A push notification template.""" + + settings: Required[Settings] + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) + for the push template. + """ + text_body: Required[str] """The body of the push notification.""" title: Required[str] """The title of the push notification.""" - - settings: Optional[Settings] - """ - The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) - for the push template. Can be omitted. - """ diff --git a/src/knock_mapi/types/request_template.py b/src/knock_mapi/types/request_template.py index 9f420fa6..2d030c6e 100644 --- a/src/knock_mapi/types/request_template.py +++ b/src/knock_mapi/types/request_template.py @@ -1,14 +1,14 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import List, Union, Optional from typing_extensions import Literal from .._models import BaseModel -__all__ = ["RequestTemplate", "Header", "QueryParam"] +__all__ = ["RequestTemplate", "HeadersRequestTemplateHeadersArray", "QueryParamsRequestTemplateQueryParamsArray"] -class Header(BaseModel): +class HeadersRequestTemplateHeadersArray(BaseModel): key: str """The key of the header.""" @@ -16,7 +16,7 @@ class Header(BaseModel): """The value of the header.""" -class QueryParam(BaseModel): +class QueryParamsRequestTemplateQueryParamsArray(BaseModel): key: str """The key of the query param.""" @@ -25,6 +25,8 @@ class QueryParam(BaseModel): class RequestTemplate(BaseModel): + """A request template for a fetch function step.""" + method: Literal["get", "post", "put", "delete", "patch"] """The HTTP method of the request.""" @@ -34,14 +36,14 @@ class RequestTemplate(BaseModel): body: Optional[str] = None """The body of the request. Only used for POST or PUT requests.""" - headers: Optional[List[Header]] = None - """A list of key-value pairs for the request headers. + headers: Union[str, List[HeadersRequestTemplateHeadersArray], None] = None + """The headers of the request. - Each object should contain key and value fields with string values. + Can be a template string or a list of key-value pairs. """ - query_params: Optional[List[QueryParam]] = None - """A list of key-value pairs for the request query params. + query_params: Union[str, List[QueryParamsRequestTemplateQueryParamsArray], None] = None + """The query params of the request. - Each object should contain key and value fields with string values. + Can be a template string or a list of key-value pairs. """ diff --git a/src/knock_mapi/types/request_template_param.py b/src/knock_mapi/types/request_template_param.py index 1d1f46b7..82fd5598 100644 --- a/src/knock_mapi/types/request_template_param.py +++ b/src/knock_mapi/types/request_template_param.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import Iterable, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypedDict -__all__ = ["RequestTemplateParam", "Header", "QueryParam"] +__all__ = ["RequestTemplateParam", "HeadersRequestTemplateHeadersArray", "QueryParamsRequestTemplateQueryParamsArray"] -class Header(TypedDict, total=False): +class HeadersRequestTemplateHeadersArray(TypedDict, total=False): key: Required[str] """The key of the header.""" @@ -16,7 +16,7 @@ class Header(TypedDict, total=False): """The value of the header.""" -class QueryParam(TypedDict, total=False): +class QueryParamsRequestTemplateQueryParamsArray(TypedDict, total=False): key: Required[str] """The key of the query param.""" @@ -25,6 +25,8 @@ class QueryParam(TypedDict, total=False): class RequestTemplateParam(TypedDict, total=False): + """A request template for a fetch function step.""" + method: Required[Literal["get", "post", "put", "delete", "patch"]] """The HTTP method of the request.""" @@ -34,14 +36,14 @@ class RequestTemplateParam(TypedDict, total=False): body: Optional[str] """The body of the request. Only used for POST or PUT requests.""" - headers: Iterable[Header] - """A list of key-value pairs for the request headers. + headers: Union[str, Iterable[HeadersRequestTemplateHeadersArray]] + """The headers of the request. - Each object should contain key and value fields with string values. + Can be a template string or a list of key-value pairs. """ - query_params: Iterable[QueryParam] - """A list of key-value pairs for the request query params. + query_params: Union[str, Iterable[QueryParamsRequestTemplateQueryParamsArray]] + """The query params of the request. - Each object should contain key and value fields with string values. + Can be a template string or a list of key-value pairs. """ diff --git a/src/knock_mapi/types/send_window.py b/src/knock_mapi/types/send_window.py index 49536d8b..c8bfbec0 100644 --- a/src/knock_mapi/types/send_window.py +++ b/src/knock_mapi/types/send_window.py @@ -11,6 +11,8 @@ class SendWindow(BaseModel): + """A send window time for a notification. Describes a single day.""" + day: Literal["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] """The day of the week.""" diff --git a/src/knock_mapi/types/send_window_param.py b/src/knock_mapi/types/send_window_param.py index 9e2f8816..f2756d34 100644 --- a/src/knock_mapi/types/send_window_param.py +++ b/src/knock_mapi/types/send_window_param.py @@ -17,6 +17,8 @@ class SendWindowParam(_SendWindowParamReservedKeywords, total=False): + """A send window time for a notification. Describes a single day.""" + day: Required[Literal["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]] """The day of the week.""" diff --git a/src/knock_mapi/types/shared/__init__.py b/src/knock_mapi/types/shared/__init__.py index e822e02d..9c4f993a 100644 --- a/src/knock_mapi/types/shared/__init__.py +++ b/src/knock_mapi/types/shared/__init__.py @@ -1,3 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .page_info import PageInfo as PageInfo +from .message_type_url_field import MessageTypeURLField as MessageTypeURLField +from .message_type_json_field import MessageTypeJsonField as MessageTypeJsonField +from .message_type_image_field import MessageTypeImageField as MessageTypeImageField +from .message_type_button_field import MessageTypeButtonField as MessageTypeButtonField +from .message_type_select_field import MessageTypeSelectField as MessageTypeSelectField +from .message_type_boolean_field import MessageTypeBooleanField as MessageTypeBooleanField +from .message_type_markdown_field import MessageTypeMarkdownField as MessageTypeMarkdownField +from .message_type_textarea_field import MessageTypeTextareaField as MessageTypeTextareaField +from .message_type_multi_select_field import MessageTypeMultiSelectField as MessageTypeMultiSelectField diff --git a/src/knock_mapi/types/shared/message_type_boolean_field.py b/src/knock_mapi/types/shared/message_type_boolean_field.py new file mode 100644 index 00000000..a23c0ecc --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_boolean_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeBooleanField", "Settings"] + + +class Settings(BaseModel): + """Settings for the boolean field.""" + + default: Optional[bool] = None + """The default value of the boolean field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeBooleanField(BaseModel): + """A boolean field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["boolean"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the boolean field.""" diff --git a/src/knock_mapi/types/shared/message_type_button_field.py b/src/knock_mapi/types/shared/message_type_button_field.py new file mode 100644 index 00000000..4c23eaf8 --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_button_field.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from ..message_type_text_field import MessageTypeTextField + +__all__ = ["MessageTypeButtonField", "Settings"] + + +class Settings(BaseModel): + """Settings for the button field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeButtonField(BaseModel): + """A button field used in a message type.""" + + action: MessageTypeTextField + """A text field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + text: MessageTypeTextField + """A text field used in a message type.""" + + type: Literal["button"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the button field.""" diff --git a/src/knock_mapi/types/shared/message_type_image_field.py b/src/knock_mapi/types/shared/message_type_image_field.py new file mode 100644 index 00000000..b29036b3 --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_image_field.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .message_type_url_field import MessageTypeURLField +from ..message_type_text_field import MessageTypeTextField + +__all__ = ["MessageTypeImageField", "Settings"] + + +class Settings(BaseModel): + """Settings for the image field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeImageField(BaseModel): + """An image field used in a message type.""" + + action: MessageTypeTextField + """A text field used in a message type.""" + + alt: MessageTypeTextField + """A text field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["image"] + """The type of the field.""" + + url: MessageTypeURLField + """A URL field used in a message type.""" + + settings: Optional[Settings] = None + """Settings for the image field.""" diff --git a/src/knock_mapi/types/shared/message_type_json_field.py b/src/knock_mapi/types/shared/message_type_json_field.py new file mode 100644 index 00000000..dc0611bd --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_json_field.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["MessageTypeJsonField", "Settings"] + + +class Settings(BaseModel): + """Settings for the json field.""" + + default: Optional[object] = None + """The default value of the JSON field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + schema_: Optional[object] = FieldInfo(alias="schema", default=None) + """A JSON schema used to validate the structure of the JSON provided. + + Must be a valid JSON schema. + """ + + +class MessageTypeJsonField(BaseModel): + """A JSON field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["json"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the json field.""" diff --git a/src/knock_mapi/types/shared/message_type_markdown_field.py b/src/knock_mapi/types/shared/message_type_markdown_field.py new file mode 100644 index 00000000..dc03c59d --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_markdown_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeMarkdownField", "Settings"] + + +class Settings(BaseModel): + """Settings for the markdown field.""" + + default: Optional[str] = None + """The default value of the markdown field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeMarkdownField(BaseModel): + """A markdown field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["markdown"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the markdown field.""" diff --git a/src/knock_mapi/types/shared/message_type_multi_select_field.py b/src/knock_mapi/types/shared/message_type_multi_select_field.py new file mode 100644 index 00000000..eb865ada --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_multi_select_field.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeMultiSelectField", "Settings", "SettingsOption"] + + +class SettingsOption(BaseModel): + value: str + """The value for the option.""" + + label: Optional[str] = None + """The display label for the option.""" + + +class Settings(BaseModel): + """Settings for the multi_select field.""" + + default: Optional[List[str]] = None + """The default values for the multi-select field.""" + + description: Optional[str] = None + + options: Optional[List[SettingsOption]] = None + """The available options for the multi-select field.""" + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeMultiSelectField(BaseModel): + """A multi-select field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + settings: Settings + """Settings for the multi_select field.""" + + type: Literal["multi_select"] + """The type of the field.""" diff --git a/src/knock_mapi/types/shared/message_type_select_field.py b/src/knock_mapi/types/shared/message_type_select_field.py new file mode 100644 index 00000000..5a2de7cd --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_select_field.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeSelectField", "Settings", "SettingsOption"] + + +class SettingsOption(BaseModel): + value: str + """The value for the option.""" + + label: Optional[str] = None + """The display label for the option.""" + + +class Settings(BaseModel): + """Settings for the select field.""" + + default: Optional[str] = None + """The default value for the select field.""" + + description: Optional[str] = None + + options: Optional[List[SettingsOption]] = None + """The available options for the select field.""" + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeSelectField(BaseModel): + """A select field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + settings: Settings + """Settings for the select field.""" + + type: Literal["select"] + """The type of the field.""" diff --git a/src/knock_mapi/types/shared/message_type_textarea_field.py b/src/knock_mapi/types/shared/message_type_textarea_field.py new file mode 100644 index 00000000..521ba704 --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_textarea_field.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeTextareaField", "Settings"] + + +class Settings(BaseModel): + """Settings for the textarea field.""" + + default: Optional[str] = None + """The default value of the textarea field.""" + + description: Optional[str] = None + + max_length: Optional[int] = None + + min_length: Optional[int] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeTextareaField(BaseModel): + """A textarea field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["textarea"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the textarea field.""" diff --git a/src/knock_mapi/types/shared/message_type_url_field.py b/src/knock_mapi/types/shared/message_type_url_field.py new file mode 100644 index 00000000..0b883a5a --- /dev/null +++ b/src/knock_mapi/types/shared/message_type_url_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["MessageTypeURLField", "Settings"] + + +class Settings(BaseModel): + """Settings for the url field.""" + + default: Optional[str] = None + """The default value of the URL field.""" + + description: Optional[str] = None + + placeholder: Optional[str] = None + + required: Optional[bool] = None + """Whether the field is required.""" + + +class MessageTypeURLField(BaseModel): + """A URL field used in a message type.""" + + key: str + """The unique key of the field.""" + + label: Optional[str] = None + """The label of the field.""" + + type: Literal["url"] + """The type of the field.""" + + settings: Optional[Settings] = None + """Settings for the url field.""" diff --git a/src/knock_mapi/types/shared/page_info.py b/src/knock_mapi/types/shared/page_info.py index c795ac52..088aead7 100644 --- a/src/knock_mapi/types/shared/page_info.py +++ b/src/knock_mapi/types/shared/page_info.py @@ -8,6 +8,8 @@ class PageInfo(BaseModel): + """The information about a paginated result.""" + page_size: int """The number of entries to fetch per-page.""" diff --git a/src/knock_mapi/types/shared_params/__init__.py b/src/knock_mapi/types/shared_params/__init__.py new file mode 100644 index 00000000..1f6afa77 --- /dev/null +++ b/src/knock_mapi/types/shared_params/__init__.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .message_type_url_field import MessageTypeURLField as MessageTypeURLField +from .message_type_json_field import MessageTypeJsonField as MessageTypeJsonField +from .message_type_image_field import MessageTypeImageField as MessageTypeImageField +from .message_type_button_field import MessageTypeButtonField as MessageTypeButtonField +from .message_type_select_field import MessageTypeSelectField as MessageTypeSelectField +from .message_type_boolean_field import MessageTypeBooleanField as MessageTypeBooleanField +from .message_type_markdown_field import MessageTypeMarkdownField as MessageTypeMarkdownField +from .message_type_textarea_field import MessageTypeTextareaField as MessageTypeTextareaField +from .message_type_multi_select_field import MessageTypeMultiSelectField as MessageTypeMultiSelectField diff --git a/src/knock_mapi/types/shared_params/message_type_boolean_field.py b/src/knock_mapi/types/shared_params/message_type_boolean_field.py new file mode 100644 index 00000000..189d5224 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_boolean_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeBooleanField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the boolean field.""" + + default: bool + """The default value of the boolean field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeBooleanField(TypedDict, total=False): + """A boolean field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["boolean"]] + """The type of the field.""" + + settings: Settings + """Settings for the boolean field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_button_field.py b/src/knock_mapi/types/shared_params/message_type_button_field.py new file mode 100644 index 00000000..909388ce --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_button_field.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from ..message_type_text_field_param import MessageTypeTextFieldParam + +__all__ = ["MessageTypeButtonField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the button field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeButtonField(TypedDict, total=False): + """A button field used in a message type.""" + + action: Required[MessageTypeTextFieldParam] + """A text field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + text: Required[MessageTypeTextFieldParam] + """A text field used in a message type.""" + + type: Required[Literal["button"]] + """The type of the field.""" + + settings: Settings + """Settings for the button field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_image_field.py b/src/knock_mapi/types/shared_params/message_type_image_field.py new file mode 100644 index 00000000..92744c10 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_image_field.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .message_type_url_field import MessageTypeURLField +from ..message_type_text_field_param import MessageTypeTextFieldParam + +__all__ = ["MessageTypeImageField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the image field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeImageField(TypedDict, total=False): + """An image field used in a message type.""" + + action: Required[MessageTypeTextFieldParam] + """A text field used in a message type.""" + + alt: Required[MessageTypeTextFieldParam] + """A text field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["image"]] + """The type of the field.""" + + url: Required[MessageTypeURLField] + """A URL field used in a message type.""" + + settings: Settings + """Settings for the image field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_json_field.py b/src/knock_mapi/types/shared_params/message_type_json_field.py new file mode 100644 index 00000000..4dc2cb85 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_json_field.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeJsonField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the json field.""" + + default: Optional[object] + """The default value of the JSON field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + schema: Optional[object] + """A JSON schema used to validate the structure of the JSON provided. + + Must be a valid JSON schema. + """ + + +class MessageTypeJsonField(TypedDict, total=False): + """A JSON field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["json"]] + """The type of the field.""" + + settings: Settings + """Settings for the json field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_markdown_field.py b/src/knock_mapi/types/shared_params/message_type_markdown_field.py new file mode 100644 index 00000000..06d52dce --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_markdown_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeMarkdownField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the markdown field.""" + + default: str + """The default value of the markdown field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeMarkdownField(TypedDict, total=False): + """A markdown field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["markdown"]] + """The type of the field.""" + + settings: Settings + """Settings for the markdown field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_multi_select_field.py b/src/knock_mapi/types/shared_params/message_type_multi_select_field.py new file mode 100644 index 00000000..88c0d043 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_multi_select_field.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["MessageTypeMultiSelectField", "Settings", "SettingsOption"] + + +class SettingsOption(TypedDict, total=False): + value: Required[str] + """The value for the option.""" + + label: str + """The display label for the option.""" + + +class Settings(TypedDict, total=False): + """Settings for the multi_select field.""" + + default: Optional[SequenceNotStr[str]] + """The default values for the multi-select field.""" + + description: Optional[str] + + options: Iterable[SettingsOption] + """The available options for the multi-select field.""" + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeMultiSelectField(TypedDict, total=False): + """A multi-select field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + settings: Required[Settings] + """Settings for the multi_select field.""" + + type: Required[Literal["multi_select"]] + """The type of the field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_select_field.py b/src/knock_mapi/types/shared_params/message_type_select_field.py new file mode 100644 index 00000000..431c3128 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_select_field.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeSelectField", "Settings", "SettingsOption"] + + +class SettingsOption(TypedDict, total=False): + value: Required[str] + """The value for the option.""" + + label: str + """The display label for the option.""" + + +class Settings(TypedDict, total=False): + """Settings for the select field.""" + + default: Optional[str] + """The default value for the select field.""" + + description: Optional[str] + + options: Iterable[SettingsOption] + """The available options for the select field.""" + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeSelectField(TypedDict, total=False): + """A select field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + settings: Required[Settings] + """Settings for the select field.""" + + type: Required[Literal["select"]] + """The type of the field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_textarea_field.py b/src/knock_mapi/types/shared_params/message_type_textarea_field.py new file mode 100644 index 00000000..e56977e4 --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_textarea_field.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeTextareaField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the textarea field.""" + + default: Optional[str] + """The default value of the textarea field.""" + + description: Optional[str] + + max_length: int + + min_length: int + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeTextareaField(TypedDict, total=False): + """A textarea field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["textarea"]] + """The type of the field.""" + + settings: Settings + """Settings for the textarea field.""" diff --git a/src/knock_mapi/types/shared_params/message_type_url_field.py b/src/knock_mapi/types/shared_params/message_type_url_field.py new file mode 100644 index 00000000..10e5db4c --- /dev/null +++ b/src/knock_mapi/types/shared_params/message_type_url_field.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["MessageTypeURLField", "Settings"] + + +class Settings(TypedDict, total=False): + """Settings for the url field.""" + + default: Optional[str] + """The default value of the URL field.""" + + description: Optional[str] + + placeholder: Optional[str] + + required: bool + """Whether the field is required.""" + + +class MessageTypeURLField(TypedDict, total=False): + """A URL field used in a message type.""" + + key: Required[str] + """The unique key of the field.""" + + label: Required[Optional[str]] + """The label of the field.""" + + type: Required[Literal["url"]] + """The type of the field.""" + + settings: Settings + """Settings for the url field.""" diff --git a/src/knock_mapi/types/sms_channel_settings.py b/src/knock_mapi/types/sms_channel_settings.py index 3298e194..f5c5657b 100644 --- a/src/knock_mapi/types/sms_channel_settings.py +++ b/src/knock_mapi/types/sms_channel_settings.py @@ -8,5 +8,10 @@ class SMSChannelSettings(BaseModel): + """SMS channel settings. + + Only used as configuration as part of a workflow channel step. + """ + link_tracking: Optional[bool] = None """Whether to track link clicks on SMS notifications.""" diff --git a/src/knock_mapi/types/sms_channel_settings_param.py b/src/knock_mapi/types/sms_channel_settings_param.py index 489eaa71..2ac1b975 100644 --- a/src/knock_mapi/types/sms_channel_settings_param.py +++ b/src/knock_mapi/types/sms_channel_settings_param.py @@ -8,5 +8,10 @@ class SMSChannelSettingsParam(TypedDict, total=False): + """SMS channel settings. + + Only used as configuration as part of a workflow channel step. + """ + link_tracking: bool """Whether to track link clicks on SMS notifications.""" diff --git a/src/knock_mapi/types/sms_template.py b/src/knock_mapi/types/sms_template.py index 53f9c74f..68ca41eb 100644 --- a/src/knock_mapi/types/sms_template.py +++ b/src/knock_mapi/types/sms_template.py @@ -8,6 +8,10 @@ class Settings(BaseModel): + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) for the SMS template. + """ + payload_overrides: Optional[str] = None """A JSON object that overrides the payload sent to the SMS provider.""" @@ -19,6 +23,8 @@ class Settings(BaseModel): class SMSTemplate(BaseModel): + """An SMS template.""" + text_body: str """The message of the SMS.""" diff --git a/src/knock_mapi/types/sms_template_param.py b/src/knock_mapi/types/sms_template_param.py index f289f3e8..f3ef8700 100644 --- a/src/knock_mapi/types/sms_template_param.py +++ b/src/knock_mapi/types/sms_template_param.py @@ -9,6 +9,10 @@ class Settings(TypedDict, total=False): + """ + The [settings](https://docs.knock.app/integrations/sms/settings-and-overrides) for the SMS template. + """ + payload_overrides: Optional[str] """A JSON object that overrides the payload sent to the SMS provider.""" @@ -20,6 +24,8 @@ class Settings(TypedDict, total=False): class SMSTemplateParam(TypedDict, total=False): + """An SMS template.""" + text_body: Required[str] """The message of the SMS.""" diff --git a/src/knock_mapi/types/static_audience.py b/src/knock_mapi/types/static_audience.py new file mode 100644 index 00000000..e8c08a01 --- /dev/null +++ b/src/knock_mapi/types/static_audience.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StaticAudience"] + + +class StaticAudience(BaseModel): + """A static audience where members are explicitly added or removed via the API.""" + + created_at: datetime + """The timestamp of when the audience was created.""" + + environment: str + """The slug of the environment in which the audience exists.""" + + key: str + """The unique key of the audience.""" + + name: str + """The name of the audience.""" + + type: Literal["static"] + """The type of audience. Always `static` for static audiences.""" + + updated_at: datetime + """The timestamp of when the audience was last updated.""" + + description: Optional[str] = None + """A description of the audience.""" + + sha: Optional[str] = None + """The SHA hash of the audience data.""" diff --git a/src/knock_mapi/types/translation.py b/src/knock_mapi/types/translation.py index 5e5e252d..310ee786 100644 --- a/src/knock_mapi/types/translation.py +++ b/src/knock_mapi/types/translation.py @@ -9,6 +9,8 @@ class Translation(BaseModel): + """A translation object.""" + content: str """ A JSON encoded string containing the key-value pairs of translation references diff --git a/src/knock_mapi/types/translation_list_params.py b/src/knock_mapi/types/translation_list_params.py index b0d08765..17c55adf 100644 --- a/src/knock_mapi/types/translation_list_params.py +++ b/src/knock_mapi/types/translation_list_params.py @@ -20,6 +20,12 @@ class TranslationListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + format: Literal["json", "po"] """Optionally specify the returned content format. diff --git a/src/knock_mapi/types/translation_retrieve_params.py b/src/knock_mapi/types/translation_retrieve_params.py index 727ab24e..3e00a628 100644 --- a/src/knock_mapi/types/translation_retrieve_params.py +++ b/src/knock_mapi/types/translation_retrieve_params.py @@ -14,6 +14,12 @@ class TranslationRetrieveParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + format: Literal["json", "po"] """Optionally specify the returned content format. diff --git a/src/knock_mapi/types/translation_retrieve_response.py b/src/knock_mapi/types/translation_retrieve_response.py index ed47f7c6..4e9e5a7d 100644 --- a/src/knock_mapi/types/translation_retrieve_response.py +++ b/src/knock_mapi/types/translation_retrieve_response.py @@ -7,5 +7,7 @@ class TranslationRetrieveResponse(BaseModel): + """Wraps the Translation response under the `translation` key.""" + translation: Translation """A translation object.""" diff --git a/src/knock_mapi/types/translation_upsert_params.py b/src/knock_mapi/types/translation_upsert_params.py index a134c5d7..86e96ea7 100644 --- a/src/knock_mapi/types/translation_upsert_params.py +++ b/src/knock_mapi/types/translation_upsert_params.py @@ -23,6 +23,12 @@ class TranslationUpsertParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit: bool """Whether to commit the resource at the same time as modifying it.""" @@ -37,6 +43,10 @@ class TranslationUpsertParams(TypedDict, total=False): class Translation(TypedDict, total=False): + """ + A translation object with a content attribute used to update or create a translation. + """ + content: Required[str] """ A JSON encoded string containing the key-value pairs of translation references diff --git a/src/knock_mapi/types/translation_upsert_response.py b/src/knock_mapi/types/translation_upsert_response.py index bf265b76..533daf75 100644 --- a/src/knock_mapi/types/translation_upsert_response.py +++ b/src/knock_mapi/types/translation_upsert_response.py @@ -7,5 +7,7 @@ class TranslationUpsertResponse(BaseModel): + """Wraps the Translation response under the `translation` key.""" + translation: Translation """A translation object.""" diff --git a/src/knock_mapi/types/translation_validate_params.py b/src/knock_mapi/types/translation_validate_params.py index ed32ad9f..e926ed96 100644 --- a/src/knock_mapi/types/translation_validate_params.py +++ b/src/knock_mapi/types/translation_validate_params.py @@ -17,8 +17,18 @@ class TranslationValidateParams(TypedDict, total=False): translation. """ + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + class Translation(TypedDict, total=False): + """ + A translation object with a content attribute used to update or create a translation. + """ + content: Required[str] """ A JSON encoded string containing the key-value pairs of translation references diff --git a/src/knock_mapi/types/translation_validate_response.py b/src/knock_mapi/types/translation_validate_response.py index 5bebf082..ac4fcc2d 100644 --- a/src/knock_mapi/types/translation_validate_response.py +++ b/src/knock_mapi/types/translation_validate_response.py @@ -7,5 +7,7 @@ class TranslationValidateResponse(BaseModel): + """Wraps the Translation response under the `translation` key.""" + translation: Translation """A translation object.""" diff --git a/src/knock_mapi/types/variable.py b/src/knock_mapi/types/variable.py index 45fdb0bc..aacb077f 100644 --- a/src/knock_mapi/types/variable.py +++ b/src/knock_mapi/types/variable.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Dict, Optional from datetime import datetime from typing_extensions import Literal @@ -10,6 +10,8 @@ class Variable(BaseModel): + """An environment variable object.""" + inserted_at: datetime """The timestamp of when the variable was created.""" @@ -22,8 +24,14 @@ class Variable(BaseModel): updated_at: datetime """The timestamp of when the variable was last updated.""" - value: str - """The value of the variable.""" - description: Optional[str] = None """The description of the variable.""" + + environment_values: Optional[Dict[str, Optional[str]]] = None + """A map of environment slugs to their override values. + + Only present for project-scoped responses. + """ + + value: Optional[str] = None + """The default value of the variable. For secret variables, this is obfuscated.""" diff --git a/src/knock_mapi/types/variable_list_params.py b/src/knock_mapi/types/variable_list_params.py index 5f664e19..8bb04a98 100644 --- a/src/knock_mapi/types/variable_list_params.py +++ b/src/knock_mapi/types/variable_list_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["VariableListParams"] @@ -17,5 +17,14 @@ class VariableListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + limit: int """The number of entries to fetch per-page.""" + + type: Literal["public", "secret"] + """Filter variables by type. Supports 'public' or 'secret'.""" diff --git a/src/knock_mapi/types/webhook_template.py b/src/knock_mapi/types/webhook_template.py index d62790e6..e9ebbd05 100644 --- a/src/knock_mapi/types/webhook_template.py +++ b/src/knock_mapi/types/webhook_template.py @@ -25,6 +25,11 @@ class QueryParam(BaseModel): class WebhookTemplate(BaseModel): + """A webhook template. + + By default, a webhook step will use the request settings you configured in your webhook channel. You can override this as you see fit on a per-step basis. + """ + method: Literal["get", "post", "put", "delete", "patch"] """The HTTP method of the webhook.""" diff --git a/src/knock_mapi/types/webhook_template_param.py b/src/knock_mapi/types/webhook_template_param.py index 29af1d6e..39e6100d 100644 --- a/src/knock_mapi/types/webhook_template_param.py +++ b/src/knock_mapi/types/webhook_template_param.py @@ -25,6 +25,11 @@ class QueryParam(TypedDict, total=False): class WebhookTemplateParam(TypedDict, total=False): + """A webhook template. + + By default, a webhook step will use the request settings you configured in your webhook channel. You can override this as you see fit on a per-step basis. + """ + method: Required[Literal["get", "post", "put", "delete", "patch"]] """The HTTP method of the webhook.""" diff --git a/src/knock_mapi/types/workflow.py b/src/knock_mapi/types/workflow.py index 25b2a922..f325b5b0 100644 --- a/src/knock_mapi/types/workflow.py +++ b/src/knock_mapi/types/workflow.py @@ -6,7 +6,6 @@ from datetime import datetime from typing_extensions import Literal -from .._compat import PYDANTIC_V2 from .._models import BaseModel from .condition_group import ConditionGroup @@ -14,6 +13,8 @@ class Settings(BaseModel): + """A map of workflow settings.""" + is_commercial: Optional[bool] = None """Whether the workflow is commercial. Defaults to false.""" @@ -26,6 +27,11 @@ class Settings(BaseModel): class Workflow(BaseModel): + """A workflow object. + + Read more in the [docs](https://docs.knock.app/concepts/workflows). + """ + active: bool """ Whether the workflow is @@ -85,9 +91,10 @@ class Workflow(BaseModel): """A map of workflow settings.""" trigger_data_json_schema: Optional[Dict[str, object]] = None - """A JSON schema for the expected structure of the workflow trigger's data payload. - - Used to validate trigger requests. Read more in the + """ + A JSON schema for the expected structure of the workflow trigger's `data` + payload (available in templates as `{{ data.field_name }}`). Used to validate + trigger requests. Read more in the [docs](https://docs.knock.app/developer-tools/validating-trigger-data). """ @@ -101,10 +108,3 @@ class Workflow(BaseModel): from .workflow_step import WorkflowStep - -if PYDANTIC_V2: - Workflow.model_rebuild() - Settings.model_rebuild() -else: - Workflow.update_forward_refs() # type: ignore - Settings.update_forward_refs() # type: ignore diff --git a/src/knock_mapi/types/workflow_activate_params.py b/src/knock_mapi/types/workflow_activate_params.py index 5116fc28..157eb6e0 100644 --- a/src/knock_mapi/types/workflow_activate_params.py +++ b/src/knock_mapi/types/workflow_activate_params.py @@ -16,3 +16,9 @@ class WorkflowActivateParams(TypedDict, total=False): Set to `true` by default, which will activate the workflow. """ + + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ diff --git a/src/knock_mapi/types/workflow_activate_response.py b/src/knock_mapi/types/workflow_activate_response.py index dc6c7ecb..1528f12d 100644 --- a/src/knock_mapi/types/workflow_activate_response.py +++ b/src/knock_mapi/types/workflow_activate_response.py @@ -2,20 +2,19 @@ from __future__ import annotations -from .._compat import PYDANTIC_V2 from .._models import BaseModel __all__ = ["WorkflowActivateResponse"] class WorkflowActivateResponse(BaseModel): + """Wraps the Workflow response under the `workflow` key.""" + workflow: "Workflow" - """A workflow object.""" + """A workflow object. + Read more in the [docs](https://docs.knock.app/concepts/workflows). + """ -from .workflow import Workflow -if PYDANTIC_V2: - WorkflowActivateResponse.model_rebuild() -else: - WorkflowActivateResponse.update_forward_refs() # type: ignore +from .workflow import Workflow diff --git a/src/knock_mapi/types/workflow_ai_agent_step.py b/src/knock_mapi/types/workflow_ai_agent_step.py new file mode 100644 index 00000000..a9a21d18 --- /dev/null +++ b/src/knock_mapi/types/workflow_ai_agent_step.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .condition_group import ConditionGroup + +__all__ = ["WorkflowAIAgentStep", "Settings"] + + +class Settings(BaseModel): + """The settings for the AI agent step.""" + + model: str + """The AI model to use in `provider:model` format (e.g. + + `anthropic:claude-haiku-4-5`, `openai:gpt-5.2-chat-latest`). See the + documentation for a list of supported models. + """ + + request_prompt: str + """The prompt template for the AI request. Supports Liquid templating.""" + + response_type: Literal["text", "json"] + """The type of response to expect from the AI model.""" + + halt_on_error: Optional[bool] = None + """Whether to halt the workflow if the AI fetch fails.""" + + response_schema: Optional[str] = None + """A JSON schema string for structured output. + + Required when `response_type` is `json`. Must not be set when `response_type` is + `text`. + """ + + web_search_enabled: Optional[bool] = None + """Whether to enable web search for the AI request.""" + + +class WorkflowAIAgentStep(BaseModel): + """An AI agent function step. + + Fetches data from an AI model and merges it into the workflow's `data` scope for use in later steps. Supports Liquid templating in the prompt. Read more in the [docs](https://docs.knock.app/designing-workflows/ai-agent-function). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Settings + """The settings for the AI agent step.""" + + type: Literal["ai_agent"] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_ai_agent_step_param.py b/src/knock_mapi/types/workflow_ai_agent_step_param.py new file mode 100644 index 00000000..412e9f13 --- /dev/null +++ b/src/knock_mapi/types/workflow_ai_agent_step_param.py @@ -0,0 +1,68 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .condition_group_param import ConditionGroupParam + +__all__ = ["WorkflowAIAgentStepParam", "Settings"] + + +class Settings(TypedDict, total=False): + """The settings for the AI agent step.""" + + model: Required[str] + """The AI model to use in `provider:model` format (e.g. + + `anthropic:claude-haiku-4-5`, `openai:gpt-5.2-chat-latest`). See the + documentation for a list of supported models. + """ + + request_prompt: Required[str] + """The prompt template for the AI request. Supports Liquid templating.""" + + response_type: Required[Literal["text", "json"]] + """The type of response to expect from the AI model.""" + + halt_on_error: Optional[bool] + """Whether to halt the workflow if the AI fetch fails.""" + + response_schema: Optional[str] + """A JSON schema string for structured output. + + Required when `response_type` is `json`. Must not be set when `response_type` is + `text`. + """ + + web_search_enabled: Optional[bool] + """Whether to enable web search for the AI request.""" + + +class WorkflowAIAgentStepParam(TypedDict, total=False): + """An AI agent function step. + + Fetches data from an AI model and merges it into the workflow's `data` scope for use in later steps. Supports Liquid templating in the prompt. Read more in the [docs](https://docs.knock.app/designing-workflows/ai-agent-function). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Required[Settings] + """The settings for the AI agent step.""" + + type: Required[Literal["ai_agent"]] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_batch_step.py b/src/knock_mapi/types/workflow_batch_step.py index edf6d176..68ecbc24 100644 --- a/src/knock_mapi/types/workflow_batch_step.py +++ b/src/knock_mapi/types/workflow_batch_step.py @@ -10,6 +10,8 @@ class Settings(BaseModel): + """The settings for the batch step.""" + batch_execution_mode: Optional[Literal["accumulate", "flush_leading"]] = None """The execution mode of the batch step. @@ -55,15 +57,11 @@ class Settings(BaseModel): class WorkflowBatchStep(BaseModel): - description: Optional[str] = None - """An arbitrary string attached to a workflow step. + """A batch function step. - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/batch-function). """ - name: str - """A name for the workflow step.""" - ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -72,3 +70,12 @@ class WorkflowBatchStep(BaseModel): type: Literal["batch"] """The type of the workflow step.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_batch_step_param.py b/src/knock_mapi/types/workflow_batch_step_param.py index 5c56ca31..40e4ca8f 100644 --- a/src/knock_mapi/types/workflow_batch_step_param.py +++ b/src/knock_mapi/types/workflow_batch_step_param.py @@ -11,6 +11,8 @@ class Settings(TypedDict, total=False): + """The settings for the batch step.""" + batch_execution_mode: Optional[Literal["accumulate", "flush_leading"]] """The execution mode of the batch step. @@ -56,15 +58,11 @@ class Settings(TypedDict, total=False): class WorkflowBatchStepParam(TypedDict, total=False): - description: Required[Optional[str]] - """An arbitrary string attached to a workflow step. + """A batch function step. - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/batch-function). """ - name: Required[str] - """A name for the workflow step.""" - ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -73,3 +71,12 @@ class WorkflowBatchStepParam(TypedDict, total=False): type: Required[Literal["batch"]] """The type of the workflow step.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_branch_step.py b/src/knock_mapi/types/workflow_branch_step.py index b563ff0e..f282ce7a 100644 --- a/src/knock_mapi/types/workflow_branch_step.py +++ b/src/knock_mapi/types/workflow_branch_step.py @@ -5,7 +5,6 @@ from typing import List, Optional from typing_extensions import Literal -from .._compat import PYDANTIC_V2 from .._models import BaseModel from .condition_group import ConditionGroup @@ -13,6 +12,8 @@ class Branch(BaseModel): + """A branch in a branch step.""" + conditions: Optional[ConditionGroup] = None """A group of conditions to be evaluated.""" @@ -23,21 +24,20 @@ class Branch(BaseModel): """A list of steps that will be executed if the branch is chosen.""" terminates: Optional[bool] = None - """If the workflow should halt at the end of the branch.""" + """If the workflow should halt at the end of the branch. + Defaults to false if not provided. + """ -class WorkflowBranchStep(BaseModel): - branches: List[Branch] - """A list of workflow branches to be evaluated.""" - description: str - """An arbitrary string attached to a workflow step. +class WorkflowBranchStep(BaseModel): + """A branch function step. - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/branch-function). """ - name: str - """A name for the workflow step.""" + branches: List[Branch] + """A list of workflow branches to be evaluated.""" ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -45,12 +45,14 @@ class WorkflowBranchStep(BaseModel): type: Literal["branch"] """The type of step.""" + description: Optional[str] = None + """An arbitrary string attached to a workflow step. -from .workflow_step import WorkflowStep + Useful for adding notes about the workflow for internal purposes. + """ -if PYDANTIC_V2: - WorkflowBranchStep.model_rebuild() - Branch.model_rebuild() -else: - WorkflowBranchStep.update_forward_refs() # type: ignore - Branch.update_forward_refs() # type: ignore + name: Optional[str] = None + """A name for the workflow step.""" + + +from .workflow_step import WorkflowStep diff --git a/src/knock_mapi/types/workflow_branch_step_param.py b/src/knock_mapi/types/workflow_branch_step_param.py index 85e25055..efc3e4dc 100644 --- a/src/knock_mapi/types/workflow_branch_step_param.py +++ b/src/knock_mapi/types/workflow_branch_step_param.py @@ -11,6 +11,8 @@ class Branch(TypedDict, total=False): + """A branch in a branch step.""" + conditions: Optional[ConditionGroupParam] """A group of conditions to be evaluated.""" @@ -21,21 +23,20 @@ class Branch(TypedDict, total=False): """A list of steps that will be executed if the branch is chosen.""" terminates: bool - """If the workflow should halt at the end of the branch.""" + """If the workflow should halt at the end of the branch. + Defaults to false if not provided. + """ -class WorkflowBranchStepParam(TypedDict, total=False): - branches: Required[Iterable[Branch]] - """A list of workflow branches to be evaluated.""" - description: Required[str] - """An arbitrary string attached to a workflow step. +class WorkflowBranchStepParam(TypedDict, total=False): + """A branch function step. - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/branch-function). """ - name: Required[str] - """A name for the workflow step.""" + branches: Required[Iterable[Branch]] + """A list of workflow branches to be evaluated.""" ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -43,5 +44,14 @@ class WorkflowBranchStepParam(TypedDict, total=False): type: Required[Literal["branch"]] """The type of step.""" + description: str + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + from .workflow_step_param import WorkflowStepParam diff --git a/src/knock_mapi/types/workflow_channel_step.py b/src/knock_mapi/types/workflow_channel_step.py deleted file mode 100644 index 2f2e93ad..00000000 --- a/src/knock_mapi/types/workflow_channel_step.py +++ /dev/null @@ -1,78 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Union, Optional -from typing_extensions import Literal, TypeAlias - -from .._models import BaseModel -from .send_window import SendWindow -from .sms_template import SMSTemplate -from .chat_template import ChatTemplate -from .push_template import PushTemplate -from .email_template import EmailTemplate -from .condition_group import ConditionGroup -from .webhook_template import WebhookTemplate -from .in_app_feed_template import InAppFeedTemplate -from .sms_channel_settings import SMSChannelSettings -from .chat_channel_settings import ChatChannelSettings -from .push_channel_settings import PushChannelSettings -from .email_channel_settings import EmailChannelSettings -from .in_app_feed_channel_settings import InAppFeedChannelSettings - -__all__ = ["WorkflowChannelStep", "Template", "ChannelOverrides"] - -Template: TypeAlias = Union[EmailTemplate, InAppFeedTemplate, SMSTemplate, PushTemplate, ChatTemplate, WebhookTemplate] - -ChannelOverrides: TypeAlias = Union[ - EmailChannelSettings, InAppFeedChannelSettings, SMSChannelSettings, PushChannelSettings, ChatChannelSettings, None -] - - -class WorkflowChannelStep(BaseModel): - name: str - """A name for the workflow step.""" - - ref: str - """The reference key of the workflow step. Must be unique per workflow.""" - - template: Template - """The message template for the channel step. - - The shape of the template depends on the type of the channel you'll be sending - to. See below for definitions of each channel type template: email, in-app, SMS, - push, chat, and webhook. - """ - - type: Literal["channel"] - """The type of the workflow step.""" - - channel_group_key: Optional[str] = None - """ - The key of the channel group to which the channel step will be sending a - notification. A channel step can have either a channel key or a channel group - key, but not both. - """ - - channel_key: Optional[str] = None - """The key of the channel to which the channel step will be sending a notification. - - A channel step can have either a channel key or a channel group key, but not - both. - """ - - channel_overrides: Optional[ChannelOverrides] = None - """A map of channel overrides for the channel step.""" - - conditions: Optional[ConditionGroup] = None - """A group of conditions to be evaluated.""" - - description: Optional[str] = None - """An arbitrary string attached to a workflow step. - - Useful for adding notes about the workflow for internal purposes. - """ - - send_windows: Optional[List[SendWindow]] = None - """A list of send window objects. - - Must include one send window object per day of the week. - """ diff --git a/src/knock_mapi/types/workflow_channel_step_param.py b/src/knock_mapi/types/workflow_channel_step_param.py deleted file mode 100644 index 41b81c0b..00000000 --- a/src/knock_mapi/types/workflow_channel_step_param.py +++ /dev/null @@ -1,90 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict - -from .send_window_param import SendWindowParam -from .sms_template_param import SMSTemplateParam -from .chat_template_param import ChatTemplateParam -from .push_template_param import PushTemplateParam -from .email_template_param import EmailTemplateParam -from .condition_group_param import ConditionGroupParam -from .webhook_template_param import WebhookTemplateParam -from .in_app_feed_template_param import InAppFeedTemplateParam -from .sms_channel_settings_param import SMSChannelSettingsParam -from .chat_channel_settings_param import ChatChannelSettingsParam -from .push_channel_settings_param import PushChannelSettingsParam -from .email_channel_settings_param import EmailChannelSettingsParam -from .in_app_feed_channel_settings_param import InAppFeedChannelSettingsParam - -__all__ = ["WorkflowChannelStepParam", "Template", "ChannelOverrides"] - -Template: TypeAlias = Union[ - EmailTemplateParam, - InAppFeedTemplateParam, - SMSTemplateParam, - PushTemplateParam, - ChatTemplateParam, - WebhookTemplateParam, -] - -ChannelOverrides: TypeAlias = Union[ - EmailChannelSettingsParam, - InAppFeedChannelSettingsParam, - SMSChannelSettingsParam, - PushChannelSettingsParam, - ChatChannelSettingsParam, -] - - -class WorkflowChannelStepParam(TypedDict, total=False): - name: Required[str] - """A name for the workflow step.""" - - ref: Required[str] - """The reference key of the workflow step. Must be unique per workflow.""" - - template: Required[Template] - """The message template for the channel step. - - The shape of the template depends on the type of the channel you'll be sending - to. See below for definitions of each channel type template: email, in-app, SMS, - push, chat, and webhook. - """ - - type: Required[Literal["channel"]] - """The type of the workflow step.""" - - channel_group_key: Optional[str] - """ - The key of the channel group to which the channel step will be sending a - notification. A channel step can have either a channel key or a channel group - key, but not both. - """ - - channel_key: Optional[str] - """The key of the channel to which the channel step will be sending a notification. - - A channel step can have either a channel key or a channel group key, but not - both. - """ - - channel_overrides: Optional[ChannelOverrides] - """A map of channel overrides for the channel step.""" - - conditions: Optional[ConditionGroupParam] - """A group of conditions to be evaluated.""" - - description: Optional[str] - """An arbitrary string attached to a workflow step. - - Useful for adding notes about the workflow for internal purposes. - """ - - send_windows: Optional[Iterable[SendWindowParam]] - """A list of send window objects. - - Must include one send window object per day of the week. - """ diff --git a/src/knock_mapi/types/workflow_chat_step.py b/src/knock_mapi/types/workflow_chat_step.py new file mode 100644 index 00000000..2d3a967e --- /dev/null +++ b/src/knock_mapi/types/workflow_chat_step.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .chat_template import ChatTemplate +from .condition_group import ConditionGroup +from .chat_channel_settings import ChatChannelSettings + +__all__ = ["WorkflowChatStep"] + + +class WorkflowChatStep(BaseModel): + """A chat step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: ChatTemplate + """A chat template.""" + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[ChatChannelSettings] = None + """Chat channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Optional[Literal["chat"]] = None + """The type of the channel step. Always `chat` for chat steps.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_chat_step_param.py b/src/knock_mapi/types/workflow_chat_step_param.py new file mode 100644 index 00000000..8baaff9f --- /dev/null +++ b/src/knock_mapi/types/workflow_chat_step_param.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .chat_template_param import ChatTemplateParam +from .condition_group_param import ConditionGroupParam +from .chat_channel_settings_param import ChatChannelSettingsParam + +__all__ = ["WorkflowChatStepParam"] + + +class WorkflowChatStepParam(TypedDict, total=False): + """A chat step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[ChatTemplateParam] + """A chat template.""" + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[ChatChannelSettingsParam] + """Chat channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Literal["chat"] + """The type of the channel step. Always `chat` for chat steps.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_delay_step.py b/src/knock_mapi/types/workflow_delay_step.py index 3f3fe5a6..7574113e 100644 --- a/src/knock_mapi/types/workflow_delay_step.py +++ b/src/knock_mapi/types/workflow_delay_step.py @@ -11,6 +11,11 @@ class Settings(BaseModel): + """The settings for the delay step. + + Both fields can be set to compute a delay where `delay_for` is an offset from the `delay_until_field_path`. + """ + delay_for: Optional[Duration] = None """A duration of time, represented as a unit and a value.""" @@ -22,18 +27,11 @@ class Settings(BaseModel): class WorkflowDelayStep(BaseModel): - conditions: Optional[ConditionGroup] = None - """A group of conditions to be evaluated.""" + """A delay function step. - description: Optional[str] = None - """An arbitrary string attached to a workflow step. - - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/delay-function). """ - name: str - """A name for the workflow step.""" - ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -46,3 +44,15 @@ class WorkflowDelayStep(BaseModel): type: Literal["delay"] """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_delay_step_param.py b/src/knock_mapi/types/workflow_delay_step_param.py index 14a6dd3a..4750f2d8 100644 --- a/src/knock_mapi/types/workflow_delay_step_param.py +++ b/src/knock_mapi/types/workflow_delay_step_param.py @@ -12,6 +12,11 @@ class Settings(TypedDict, total=False): + """The settings for the delay step. + + Both fields can be set to compute a delay where `delay_for` is an offset from the `delay_until_field_path`. + """ + delay_for: Optional[DurationParam] """A duration of time, represented as a unit and a value.""" @@ -23,18 +28,11 @@ class Settings(TypedDict, total=False): class WorkflowDelayStepParam(TypedDict, total=False): - conditions: Required[Optional[ConditionGroupParam]] - """A group of conditions to be evaluated.""" + """A delay function step. - description: Required[Optional[str]] - """An arbitrary string attached to a workflow step. - - Useful for adding notes about the workflow for internal purposes. + Read more in the [docs](https://docs.knock.app/designing-workflows/delay-function). """ - name: Required[str] - """A name for the workflow step.""" - ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -47,3 +45,15 @@ class WorkflowDelayStepParam(TypedDict, total=False): type: Required[Literal["delay"]] """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_email_step.py b/src/knock_mapi/types/workflow_email_step.py new file mode 100644 index 00000000..7cad2a86 --- /dev/null +++ b/src/knock_mapi/types/workflow_email_step.py @@ -0,0 +1,74 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .email_template import EmailTemplate +from .condition_group import ConditionGroup +from .email_channel_settings import EmailChannelSettings + +__all__ = ["WorkflowEmailStep"] + + +class WorkflowEmailStep(BaseModel): + """An email step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: EmailTemplate + """An email message template.""" + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[EmailChannelSettings] = None + """Email channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Optional[Literal["email"]] = None + """The category of channel for this step. + + Always `email` for email steps. This identifies the type of notification (email, + sms, push, etc.) while `channel_key` specifies which configured provider + instance to use. + """ + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_email_step_param.py b/src/knock_mapi/types/workflow_email_step_param.py new file mode 100644 index 00000000..567ebd12 --- /dev/null +++ b/src/knock_mapi/types/workflow_email_step_param.py @@ -0,0 +1,75 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .email_template_param import EmailTemplateParam +from .condition_group_param import ConditionGroupParam +from .email_channel_settings_param import EmailChannelSettingsParam + +__all__ = ["WorkflowEmailStepParam"] + + +class WorkflowEmailStepParam(TypedDict, total=False): + """An email step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[EmailTemplateParam] + """An email message template.""" + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[EmailChannelSettingsParam] + """Email channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Literal["email"] + """The category of channel for this step. + + Always `email` for email steps. This identifies the type of notification (email, + sms, push, etc.) while `channel_key` specifies which configured provider + instance to use. + """ + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_fetch_step.py b/src/knock_mapi/types/workflow_fetch_step.py index 03e5a5a9..300b473b 100644 --- a/src/knock_mapi/types/workflow_fetch_step.py +++ b/src/knock_mapi/types/workflow_fetch_step.py @@ -11,8 +11,10 @@ class WorkflowFetchStep(BaseModel): - name: str - """A name for the workflow step.""" + """A fetch function step. + + Retrieves data from an external source and merges it into the workflow's `data` scope for use in later steps. Read more in the [docs](https://docs.knock.app/designing-workflows/fetch-function). + """ ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -20,7 +22,7 @@ class WorkflowFetchStep(BaseModel): settings: RequestTemplate """A request template for a fetch function step.""" - type: Literal["fetch"] + type: Literal["http_fetch"] """The type of the workflow step.""" conditions: Optional[ConditionGroup] = None @@ -31,3 +33,6 @@ class WorkflowFetchStep(BaseModel): Useful for adding notes about the workflow for internal purposes. """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_fetch_step_param.py b/src/knock_mapi/types/workflow_fetch_step_param.py index da2e56f0..0649ceaa 100644 --- a/src/knock_mapi/types/workflow_fetch_step_param.py +++ b/src/knock_mapi/types/workflow_fetch_step_param.py @@ -12,8 +12,10 @@ class WorkflowFetchStepParam(TypedDict, total=False): - name: Required[str] - """A name for the workflow step.""" + """A fetch function step. + + Retrieves data from an external source and merges it into the workflow's `data` scope for use in later steps. Read more in the [docs](https://docs.knock.app/designing-workflows/fetch-function). + """ ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -21,7 +23,7 @@ class WorkflowFetchStepParam(TypedDict, total=False): settings: Required[RequestTemplateParam] """A request template for a fetch function step.""" - type: Required[Literal["fetch"]] + type: Required[Literal["http_fetch"]] """The type of the workflow step.""" conditions: Optional[ConditionGroupParam] @@ -32,3 +34,6 @@ class WorkflowFetchStepParam(TypedDict, total=False): Useful for adding notes about the workflow for internal purposes. """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_in_app_feed_step.py b/src/knock_mapi/types/workflow_in_app_feed_step.py new file mode 100644 index 00000000..fbc87b59 --- /dev/null +++ b/src/knock_mapi/types/workflow_in_app_feed_step.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .condition_group import ConditionGroup +from .in_app_feed_template import InAppFeedTemplate +from .in_app_feed_channel_settings import InAppFeedChannelSettings + +__all__ = ["WorkflowInAppFeedStep"] + + +class WorkflowInAppFeedStep(BaseModel): + """An in-app feed step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: InAppFeedTemplate + """An in-app feed template.""" + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[InAppFeedChannelSettings] = None + """In-app feed channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Optional[Literal["in_app_feed"]] = None + """The type of the channel step. Always `in_app_feed` for in-app feed steps.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_in_app_feed_step_param.py b/src/knock_mapi/types/workflow_in_app_feed_step_param.py new file mode 100644 index 00000000..613fd1f8 --- /dev/null +++ b/src/knock_mapi/types/workflow_in_app_feed_step_param.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .condition_group_param import ConditionGroupParam +from .in_app_feed_template_param import InAppFeedTemplateParam +from .in_app_feed_channel_settings_param import InAppFeedChannelSettingsParam + +__all__ = ["WorkflowInAppFeedStepParam"] + + +class WorkflowInAppFeedStepParam(TypedDict, total=False): + """An in-app feed step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[InAppFeedTemplateParam] + """An in-app feed template.""" + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[InAppFeedChannelSettingsParam] + """In-app feed channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Literal["in_app_feed"] + """The type of the channel step. Always `in_app_feed` for in-app feed steps.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_list_params.py b/src/knock_mapi/types/workflow_list_params.py index bd5a3808..b8f930ab 100644 --- a/src/knock_mapi/types/workflow_list_params.py +++ b/src/knock_mapi/types/workflow_list_params.py @@ -20,6 +20,12 @@ class WorkflowListParams(TypedDict, total=False): before: str """The cursor to fetch entries before.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/workflow_push_step.py b/src/knock_mapi/types/workflow_push_step.py new file mode 100644 index 00000000..a7e0aeec --- /dev/null +++ b/src/knock_mapi/types/workflow_push_step.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .push_template import PushTemplate +from .condition_group import ConditionGroup +from .push_channel_settings import PushChannelSettings + +__all__ = ["WorkflowPushStep"] + + +class WorkflowPushStep(BaseModel): + """A push step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: PushTemplate + """A push notification template.""" + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[PushChannelSettings] = None + """Push channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Optional[Literal["push"]] = None + """The type of the channel step. Always `push` for push steps.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_push_step_param.py b/src/knock_mapi/types/workflow_push_step_param.py new file mode 100644 index 00000000..d58778ac --- /dev/null +++ b/src/knock_mapi/types/workflow_push_step_param.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .push_template_param import PushTemplateParam +from .condition_group_param import ConditionGroupParam +from .push_channel_settings_param import PushChannelSettingsParam + +__all__ = ["WorkflowPushStepParam"] + + +class WorkflowPushStepParam(TypedDict, total=False): + """A push step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[PushTemplateParam] + """A push notification template.""" + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[PushChannelSettingsParam] + """Push channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Literal["push"] + """The type of the channel step. Always `push` for push steps.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_random_cohort_step.py b/src/knock_mapi/types/workflow_random_cohort_step.py new file mode 100644 index 00000000..96a9a47a --- /dev/null +++ b/src/knock_mapi/types/workflow_random_cohort_step.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["WorkflowRandomCohortStep"] + + +class WorkflowRandomCohortStep(BaseModel): + """An experiment step. + + Deterministically assigns recipients to percentage-based cohorts for A/B testing and experimentation. + """ + + cohort_branches: List[object] + """A list of cohort branches. + + Must have between 2 and 10 branches, and percentages must sum to 100. + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + type: Literal["random_cohort"] + """The type of step.""" + + cohort_key: Optional[str] = None + """The key used to deterministically assign recipients to cohorts. + + Defaults to the recipient ID if not provided. + """ + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_random_cohort_step_param.py b/src/knock_mapi/types/workflow_random_cohort_step_param.py new file mode 100644 index 00000000..cd765f31 --- /dev/null +++ b/src/knock_mapi/types/workflow_random_cohort_step_param.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["WorkflowRandomCohortStepParam"] + + +class WorkflowRandomCohortStepParam(TypedDict, total=False): + """An experiment step. + + Deterministically assigns recipients to percentage-based cohorts for A/B testing and experimentation. + """ + + cohort_branches: Required[Iterable[object]] + """A list of cohort branches. + + Must have between 2 and 10 branches, and percentages must sum to 100. + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + type: Required[Literal["random_cohort"]] + """The type of step.""" + + cohort_key: Optional[str] + """The key used to deterministically assign recipients to cohorts. + + Defaults to the recipient ID if not provided. + """ + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_retrieve_params.py b/src/knock_mapi/types/workflow_retrieve_params.py index 2398fb6a..8f5c4df4 100644 --- a/src/knock_mapi/types/workflow_retrieve_params.py +++ b/src/knock_mapi/types/workflow_retrieve_params.py @@ -14,6 +14,12 @@ class WorkflowRetrieveParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + hide_uncommitted_changes: bool """Whether to hide uncommitted changes. diff --git a/src/knock_mapi/types/workflow_retrieve_response.py b/src/knock_mapi/types/workflow_retrieve_response.py new file mode 100644 index 00000000..5ab6f22d --- /dev/null +++ b/src/knock_mapi/types/workflow_retrieve_response.py @@ -0,0 +1,120 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .member_user import MemberUser +from .condition_group import ConditionGroup + +__all__ = ["WorkflowRetrieveResponse", "Settings"] + + +class Settings(BaseModel): + """A map of workflow settings.""" + + is_commercial: Optional[bool] = None + """Whether the workflow is commercial. Defaults to false.""" + + override_preferences: Optional[bool] = None + """Whether to ignore recipient preferences for a given type of notification. + + If true, will send for every channel in the workflow even if the recipient has + opted out of a certain kind. Defaults to false. + """ + + +class WorkflowRetrieveResponse(BaseModel): + """A workflow object.""" + + active: bool + """ + Whether the workflow is + [active](https://docs.knock.app/concepts/workflows#workflow-status) in the + current environment. (read-only). + """ + + created_at: datetime + """The timestamp of when the workflow was created. (read-only).""" + + environment: str + """The slug of the environment in which the workflow exists. (read-only).""" + + key: str + """The unique key string for the workflow object. + + Must be at minimum 3 characters and at maximum 255 characters in length. Must be + in the format of ^[a-z0-9_-]+$. + """ + + name: str + """A name for the workflow. Must be at maximum 255 characters in length.""" + + sha: str + """The SHA hash of the workflow data. (read-only).""" + + steps: List["WorkflowStep"] + """A list of workflow step objects in the workflow.""" + + updated_at: datetime + """The timestamp of when the workflow was last updated. (read-only).""" + + valid: bool + """Whether the workflow and its steps are in a valid state. (read-only).""" + + categories: Optional[List[str]] = None + """ + A list of + [categories](https://docs.knock.app/concepts/workflows#workflow-categories) that + the workflow belongs to. + """ + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + created_by: Optional[MemberUser] = None + """Information about a user within the Knock dashboard. + + Not to be confused with an external user (recipient) of a workflow. + """ + + deleted_at: Optional[datetime] = None + """The timestamp of when the workflow was deleted. (read-only).""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow object. + + Useful for adding notes about the workflow for internal purposes. Maximum of 280 + characters allowed. + """ + + settings: Optional[Settings] = None + """A map of workflow settings.""" + + trigger_data_json_schema: Optional[Dict[str, object]] = None + """ + A JSON schema for the expected structure of the workflow trigger's `data` + payload (available in templates as `{{ data.field_name }}`). Used to validate + trigger requests. Read more in the + [docs](https://docs.knock.app/developer-tools/validating-trigger-data). + """ + + trigger_frequency: Optional[Literal["every_trigger", "once_per_recipient", "once_per_recipient_per_tenant"]] = None + """The frequency at which the workflow should be triggered. + + One of: `once_per_recipient`, `once_per_recipient_per_tenant`, `every_trigger`. + Defaults to `every_trigger`. Read more in + [docs](https://docs.knock.app/send-notifications/triggering-workflows/overview#controlling-workflow-trigger-frequency). + """ + + updated_by: Optional[MemberUser] = None + """Information about a user within the Knock dashboard. + + Not to be confused with an external user (recipient) of a workflow. + """ + + +from .workflow_step import WorkflowStep diff --git a/src/knock_mapi/types/workflow_run_params.py b/src/knock_mapi/types/workflow_run_params.py index 62e63e40..cc7c34a7 100644 --- a/src/knock_mapi/types/workflow_run_params.py +++ b/src/knock_mapi/types/workflow_run_params.py @@ -2,19 +2,33 @@ from __future__ import annotations -from typing import Dict, List, Union, Optional +from typing import Dict, Union, Optional from typing_extensions import Required, TypeAlias, TypedDict -__all__ = ["WorkflowRunParams", "Recipient", "RecipientUnionMember1", "Actor", "ActorUnionMember1"] +from .._types import SequenceNotStr + +__all__ = [ + "WorkflowRunParams", + "Recipient", + "RecipientObjectRecipientReference", + "Actor", + "ActorObjectRecipientReference", +] class WorkflowRunParams(TypedDict, total=False): environment: Required[str] """The environment slug.""" - recipients: Required[List[Recipient]] + recipients: Required[SequenceNotStr[Recipient]] """A list of recipients to run the workflow for.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + actor: Optional[Actor] """ A recipient reference, used when referencing a recipient by either their ID (for @@ -25,25 +39,38 @@ class WorkflowRunParams(TypedDict, total=False): """A key to cancel the workflow run.""" data: Dict[str, object] - """A map of data to be used in the workflow run.""" + """A map of data to be used in the workflow run. + + The structure should conform to the workflow's `trigger_data_json_schema` if one + is defined. Available in templates as `{{ data.field_name }}`. See + [trigger data validation docs](https://docs.knock.app/developer-tools/validating-trigger-data). + """ tenant: str - """The tenant to associate the workflow run with.""" + """The tenant to associate the workflow run with. Must not contain whitespace.""" -class RecipientUnionMember1(TypedDict, total=False): +class RecipientObjectRecipientReference(TypedDict, total=False): + """An object reference.""" + id: Required[str] + """The ID of the object.""" collection: Required[str] + """The collection of the object.""" + +Recipient: TypeAlias = Union[str, RecipientObjectRecipientReference] -Recipient: TypeAlias = Union[str, RecipientUnionMember1] +class ActorObjectRecipientReference(TypedDict, total=False): + """An object reference.""" -class ActorUnionMember1(TypedDict, total=False): id: Required[str] + """The ID of the object.""" collection: Required[str] + """The collection of the object.""" -Actor: TypeAlias = Union[str, ActorUnionMember1] +Actor: TypeAlias = Union[str, ActorObjectRecipientReference] diff --git a/src/knock_mapi/types/workflow_run_response.py b/src/knock_mapi/types/workflow_run_response.py index ebbe9e22..351c3123 100644 --- a/src/knock_mapi/types/workflow_run_response.py +++ b/src/knock_mapi/types/workflow_run_response.py @@ -6,5 +6,7 @@ class WorkflowRunResponse(BaseModel): + """A response to a run workflow request.""" + workflow_run_id: str """The ID of the workflow run.""" diff --git a/src/knock_mapi/types/workflow_sms_step.py b/src/knock_mapi/types/workflow_sms_step.py new file mode 100644 index 00000000..d809fe43 --- /dev/null +++ b/src/knock_mapi/types/workflow_sms_step.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .sms_template import SMSTemplate +from .condition_group import ConditionGroup +from .sms_channel_settings import SMSChannelSettings + +__all__ = ["WorkflowSMSStep"] + + +class WorkflowSMSStep(BaseModel): + """A SMS step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: SMSTemplate + """An SMS template.""" + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[SMSChannelSettings] = None + """SMS channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Optional[Literal["sms"]] = None + """The type of the channel step. Always `sms` for SMS steps.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_sms_step_param.py b/src/knock_mapi/types/workflow_sms_step_param.py new file mode 100644 index 00000000..bb9d2a13 --- /dev/null +++ b/src/knock_mapi/types/workflow_sms_step_param.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .sms_template_param import SMSTemplateParam +from .condition_group_param import ConditionGroupParam +from .sms_channel_settings_param import SMSChannelSettingsParam + +__all__ = ["WorkflowSMSStepParam"] + + +class WorkflowSMSStepParam(TypedDict, total=False): + """A SMS step within a workflow. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[SMSTemplateParam] + """An SMS template.""" + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_overrides: Optional[SMSChannelSettingsParam] + """SMS channel settings. + + Only used as configuration as part of a workflow channel step. + """ + + channel_type: Literal["sms"] + """The type of the channel step. Always `sms` for SMS steps.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_step.py b/src/knock_mapi/types/workflow_step.py index f6ee72a2..4ac8ac68 100644 --- a/src/knock_mapi/types/workflow_step.py +++ b/src/knock_mapi/types/workflow_step.py @@ -5,37 +5,70 @@ from typing import TYPE_CHECKING, Union from typing_extensions import TypeAlias, TypeAliasType -from .._compat import PYDANTIC_V2 +from .._compat import PYDANTIC_V1 +from .workflow_sms_step import WorkflowSMSStep +from .workflow_chat_step import WorkflowChatStep +from .workflow_push_step import WorkflowPushStep from .workflow_batch_step import WorkflowBatchStep from .workflow_delay_step import WorkflowDelayStep +from .workflow_email_step import WorkflowEmailStep from .workflow_fetch_step import WorkflowFetchStep -from .workflow_channel_step import WorkflowChannelStep +from .workflow_webhook_step import WorkflowWebhookStep +from .workflow_ai_agent_step import WorkflowAIAgentStep from .workflow_throttle_step import WorkflowThrottleStep +from .workflow_in_app_feed_step import WorkflowInAppFeedStep +from .workflow_update_data_step import WorkflowUpdateDataStep +from .workflow_update_user_step import WorkflowUpdateUserStep +from .workflow_random_cohort_step import WorkflowRandomCohortStep +from .workflow_update_object_step import WorkflowUpdateObjectStep +from .workflow_update_tenant_step import WorkflowUpdateTenantStep from .workflow_trigger_workflow_step import WorkflowTriggerWorkflowStep __all__ = ["WorkflowStep"] -if TYPE_CHECKING or PYDANTIC_V2: +if TYPE_CHECKING or not PYDANTIC_V1: WorkflowStep = TypeAliasType( "WorkflowStep", Union[ - WorkflowChannelStep, + WorkflowWebhookStep, + WorkflowInAppFeedStep, + WorkflowChatStep, + WorkflowSMSStep, + WorkflowPushStep, + WorkflowEmailStep, + WorkflowAIAgentStep, WorkflowDelayStep, WorkflowBatchStep, WorkflowFetchStep, + WorkflowUpdateDataStep, + WorkflowUpdateObjectStep, + WorkflowUpdateTenantStep, + WorkflowUpdateUserStep, WorkflowThrottleStep, "WorkflowBranchStep", + WorkflowRandomCohortStep, WorkflowTriggerWorkflowStep, ], ) else: WorkflowStep: TypeAlias = Union[ - WorkflowChannelStep, + WorkflowWebhookStep, + WorkflowInAppFeedStep, + WorkflowChatStep, + WorkflowSMSStep, + WorkflowPushStep, + WorkflowEmailStep, + WorkflowAIAgentStep, WorkflowDelayStep, WorkflowBatchStep, WorkflowFetchStep, + WorkflowUpdateDataStep, + WorkflowUpdateObjectStep, + WorkflowUpdateTenantStep, + WorkflowUpdateUserStep, WorkflowThrottleStep, "WorkflowBranchStep", + WorkflowRandomCohortStep, WorkflowTriggerWorkflowStep, ] diff --git a/src/knock_mapi/types/workflow_step_param.py b/src/knock_mapi/types/workflow_step_param.py index 52d5618a..bc5f5b4b 100644 --- a/src/knock_mapi/types/workflow_step_param.py +++ b/src/knock_mapi/types/workflow_step_param.py @@ -5,37 +5,70 @@ from typing import TYPE_CHECKING, Union from typing_extensions import TypeAlias, TypeAliasType -from .._compat import PYDANTIC_V2 +from .._compat import PYDANTIC_V1 +from .workflow_sms_step_param import WorkflowSMSStepParam +from .workflow_chat_step_param import WorkflowChatStepParam +from .workflow_push_step_param import WorkflowPushStepParam from .workflow_batch_step_param import WorkflowBatchStepParam from .workflow_delay_step_param import WorkflowDelayStepParam +from .workflow_email_step_param import WorkflowEmailStepParam from .workflow_fetch_step_param import WorkflowFetchStepParam -from .workflow_channel_step_param import WorkflowChannelStepParam +from .workflow_webhook_step_param import WorkflowWebhookStepParam +from .workflow_ai_agent_step_param import WorkflowAIAgentStepParam from .workflow_throttle_step_param import WorkflowThrottleStepParam +from .workflow_in_app_feed_step_param import WorkflowInAppFeedStepParam +from .workflow_update_data_step_param import WorkflowUpdateDataStepParam +from .workflow_update_user_step_param import WorkflowUpdateUserStepParam +from .workflow_random_cohort_step_param import WorkflowRandomCohortStepParam +from .workflow_update_object_step_param import WorkflowUpdateObjectStepParam +from .workflow_update_tenant_step_param import WorkflowUpdateTenantStepParam from .workflow_trigger_workflow_step_param import WorkflowTriggerWorkflowStepParam __all__ = ["WorkflowStepParam"] -if TYPE_CHECKING or PYDANTIC_V2: +if TYPE_CHECKING or not PYDANTIC_V1: WorkflowStepParam = TypeAliasType( "WorkflowStepParam", Union[ - WorkflowChannelStepParam, + WorkflowWebhookStepParam, + WorkflowInAppFeedStepParam, + WorkflowChatStepParam, + WorkflowSMSStepParam, + WorkflowPushStepParam, + WorkflowEmailStepParam, + WorkflowAIAgentStepParam, WorkflowDelayStepParam, WorkflowBatchStepParam, WorkflowFetchStepParam, + WorkflowUpdateDataStepParam, + WorkflowUpdateObjectStepParam, + WorkflowUpdateTenantStepParam, + WorkflowUpdateUserStepParam, WorkflowThrottleStepParam, "WorkflowBranchStepParam", + WorkflowRandomCohortStepParam, WorkflowTriggerWorkflowStepParam, ], ) else: WorkflowStepParam: TypeAlias = Union[ - WorkflowChannelStepParam, + WorkflowWebhookStepParam, + WorkflowInAppFeedStepParam, + WorkflowChatStepParam, + WorkflowSMSStepParam, + WorkflowPushStepParam, + WorkflowEmailStepParam, + WorkflowAIAgentStepParam, WorkflowDelayStepParam, WorkflowBatchStepParam, WorkflowFetchStepParam, + WorkflowUpdateDataStepParam, + WorkflowUpdateObjectStepParam, + WorkflowUpdateTenantStepParam, + WorkflowUpdateUserStepParam, WorkflowThrottleStepParam, "WorkflowBranchStepParam", + WorkflowRandomCohortStepParam, WorkflowTriggerWorkflowStepParam, ] diff --git a/src/knock_mapi/types/workflow_throttle_step.py b/src/knock_mapi/types/workflow_throttle_step.py index 74f7e982..47d95a0e 100644 --- a/src/knock_mapi/types/workflow_throttle_step.py +++ b/src/knock_mapi/types/workflow_throttle_step.py @@ -11,6 +11,8 @@ class Settings(BaseModel): + """The settings for the throttle step.""" + throttle_key: Optional[str] = None """The data property to use to throttle notifications per recipient.""" @@ -32,8 +34,10 @@ class Settings(BaseModel): class WorkflowThrottleStep(BaseModel): - name: str - """A name for the workflow step.""" + """A throttle function step. + + Read more in the [docs](https://docs.knock.app/designing-workflows/throttle-function). + """ ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -52,3 +56,6 @@ class WorkflowThrottleStep(BaseModel): Useful for adding notes about the workflow for internal purposes. """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_throttle_step_param.py b/src/knock_mapi/types/workflow_throttle_step_param.py index 9b83b4a6..4a86fd53 100644 --- a/src/knock_mapi/types/workflow_throttle_step_param.py +++ b/src/knock_mapi/types/workflow_throttle_step_param.py @@ -12,6 +12,8 @@ class Settings(TypedDict, total=False): + """The settings for the throttle step.""" + throttle_key: Optional[str] """The data property to use to throttle notifications per recipient.""" @@ -33,8 +35,10 @@ class Settings(TypedDict, total=False): class WorkflowThrottleStepParam(TypedDict, total=False): - name: Required[str] - """A name for the workflow step.""" + """A throttle function step. + + Read more in the [docs](https://docs.knock.app/designing-workflows/throttle-function). + """ ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -53,3 +57,6 @@ class WorkflowThrottleStepParam(TypedDict, total=False): Useful for adding notes about the workflow for internal purposes. """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_trigger_workflow_step.py b/src/knock_mapi/types/workflow_trigger_workflow_step.py index 96e6e889..bc96ca1c 100644 --- a/src/knock_mapi/types/workflow_trigger_workflow_step.py +++ b/src/knock_mapi/types/workflow_trigger_workflow_step.py @@ -10,6 +10,8 @@ class Settings(BaseModel): + """The settings for the workflow trigger workflow step.""" + actor: Optional[str] = None """The actor to trigger the workflow with. Supports liquid.""" @@ -30,8 +32,10 @@ class Settings(BaseModel): class WorkflowTriggerWorkflowStep(BaseModel): - name: str - """A name for the workflow step.""" + """A workflow trigger function step. + + Read more in the [docs](https://docs.knock.app/designing-workflows/trigger-workflow-function). + """ ref: str """The reference key of the workflow step. Must be unique per workflow.""" @@ -47,3 +51,6 @@ class WorkflowTriggerWorkflowStep(BaseModel): description: Optional[str] = None """A description for the workflow step.""" + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_trigger_workflow_step_param.py b/src/knock_mapi/types/workflow_trigger_workflow_step_param.py index 1469d0d4..cd0a2f32 100644 --- a/src/knock_mapi/types/workflow_trigger_workflow_step_param.py +++ b/src/knock_mapi/types/workflow_trigger_workflow_step_param.py @@ -11,6 +11,8 @@ class Settings(TypedDict, total=False): + """The settings for the workflow trigger workflow step.""" + actor: str """The actor to trigger the workflow with. Supports liquid.""" @@ -31,8 +33,10 @@ class Settings(TypedDict, total=False): class WorkflowTriggerWorkflowStepParam(TypedDict, total=False): - name: Required[str] - """A name for the workflow step.""" + """A workflow trigger function step. + + Read more in the [docs](https://docs.knock.app/designing-workflows/trigger-workflow-function). + """ ref: Required[str] """The reference key of the workflow step. Must be unique per workflow.""" @@ -46,5 +50,8 @@ class WorkflowTriggerWorkflowStepParam(TypedDict, total=False): conditions: Optional[ConditionGroupParam] """A group of conditions to be evaluated.""" - description: str + description: Optional[str] """A description for the workflow step.""" + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_data_step.py b/src/knock_mapi/types/workflow_update_data_step.py new file mode 100644 index 00000000..59fc4d33 --- /dev/null +++ b/src/knock_mapi/types/workflow_update_data_step.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .condition_group import ConditionGroup + +__all__ = ["WorkflowUpdateDataStep", "Settings"] + + +class Settings(BaseModel): + """The settings for the update data step.""" + + data: str + """ + A JSON string or Liquid template that evaluates to the data to merge into the + workflow's data scope. + """ + + +class WorkflowUpdateDataStep(BaseModel): + """An update data function step. + + Merges data into the workflow's `data` scope for use in subsequent steps. + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Settings + """The settings for the update data step.""" + + type: Literal["update_data"] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_data_step_param.py b/src/knock_mapi/types/workflow_update_data_step_param.py new file mode 100644 index 00000000..677e8bbc --- /dev/null +++ b/src/knock_mapi/types/workflow_update_data_step_param.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .condition_group_param import ConditionGroupParam + +__all__ = ["WorkflowUpdateDataStepParam", "Settings"] + + +class Settings(TypedDict, total=False): + """The settings for the update data step.""" + + data: Required[str] + """ + A JSON string or Liquid template that evaluates to the data to merge into the + workflow's data scope. + """ + + +class WorkflowUpdateDataStepParam(TypedDict, total=False): + """An update data function step. + + Merges data into the workflow's `data` scope for use in subsequent steps. + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Required[Settings] + """The settings for the update data step.""" + + type: Required[Literal["update_data"]] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_object_step.py b/src/knock_mapi/types/workflow_update_object_step.py new file mode 100644 index 00000000..4257adc5 --- /dev/null +++ b/src/knock_mapi/types/workflow_update_object_step.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .condition_group import ConditionGroup + +__all__ = ["WorkflowUpdateObjectStep", "Settings"] + + +class Settings(BaseModel): + """The settings for the update object step.""" + + recipient_gid: str + """The global identifier (GID) of the object to update. + + Format: gid://Object/{collection}/{id} + """ + + update_properties: str + """ + A JSON string or Liquid template that evaluates to the properties to update on + the object. + """ + + +class WorkflowUpdateObjectStep(BaseModel): + """An update object step. + + Updates properties of a specific object referenced in the workflow. + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Settings + """The settings for the update object step.""" + + type: Literal["update_object"] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_object_step_param.py b/src/knock_mapi/types/workflow_update_object_step_param.py new file mode 100644 index 00000000..ed7a606f --- /dev/null +++ b/src/knock_mapi/types/workflow_update_object_step_param.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .condition_group_param import ConditionGroupParam + +__all__ = ["WorkflowUpdateObjectStepParam", "Settings"] + + +class Settings(TypedDict, total=False): + """The settings for the update object step.""" + + recipient_gid: Required[str] + """The global identifier (GID) of the object to update. + + Format: gid://Object/{collection}/{id} + """ + + update_properties: Required[str] + """ + A JSON string or Liquid template that evaluates to the properties to update on + the object. + """ + + +class WorkflowUpdateObjectStepParam(TypedDict, total=False): + """An update object step. + + Updates properties of a specific object referenced in the workflow. + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Required[Settings] + """The settings for the update object step.""" + + type: Required[Literal["update_object"]] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_tenant_step.py b/src/knock_mapi/types/workflow_update_tenant_step.py new file mode 100644 index 00000000..e6039939 --- /dev/null +++ b/src/knock_mapi/types/workflow_update_tenant_step.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .condition_group import ConditionGroup + +__all__ = ["WorkflowUpdateTenantStep", "Settings"] + + +class Settings(BaseModel): + """The settings for the update tenant step.""" + + recipient_mode: Literal["current", "reference"] + """The recipient mode determining how the tenant is selected. + + 'current' uses the workflow's current tenant. 'reference' uses a specific tenant + ID. + """ + + update_properties: str + """ + A JSON string or Liquid template that evaluates to the properties to update on + the tenant. + """ + + recipient_gid: Optional[str] = None + """The global identifier (GID) of the tenant to update. + + Required when recipient_mode is 'reference'. Format: gid://Object/$tenants/{id} + """ + + +class WorkflowUpdateTenantStep(BaseModel): + """An update tenant step. + + Updates properties of a specific tenant referenced in the workflow. + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Settings + """The settings for the update tenant step.""" + + type: Literal["update_tenant"] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_tenant_step_param.py b/src/knock_mapi/types/workflow_update_tenant_step_param.py new file mode 100644 index 00000000..b9e9b7df --- /dev/null +++ b/src/knock_mapi/types/workflow_update_tenant_step_param.py @@ -0,0 +1,61 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .condition_group_param import ConditionGroupParam + +__all__ = ["WorkflowUpdateTenantStepParam", "Settings"] + + +class Settings(TypedDict, total=False): + """The settings for the update tenant step.""" + + recipient_mode: Required[Literal["current", "reference"]] + """The recipient mode determining how the tenant is selected. + + 'current' uses the workflow's current tenant. 'reference' uses a specific tenant + ID. + """ + + update_properties: Required[str] + """ + A JSON string or Liquid template that evaluates to the properties to update on + the tenant. + """ + + recipient_gid: Optional[str] + """The global identifier (GID) of the tenant to update. + + Required when recipient_mode is 'reference'. Format: gid://Object/$tenants/{id} + """ + + +class WorkflowUpdateTenantStepParam(TypedDict, total=False): + """An update tenant step. + + Updates properties of a specific tenant referenced in the workflow. + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Required[Settings] + """The settings for the update tenant step.""" + + type: Required[Literal["update_tenant"]] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_user_step.py b/src/knock_mapi/types/workflow_update_user_step.py new file mode 100644 index 00000000..aa858bba --- /dev/null +++ b/src/knock_mapi/types/workflow_update_user_step.py @@ -0,0 +1,59 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .condition_group import ConditionGroup + +__all__ = ["WorkflowUpdateUserStep", "Settings"] + + +class Settings(BaseModel): + """The settings for the update user step.""" + + recipient_mode: Literal["current", "reference"] + """The recipient mode determining how the user is selected. + + 'current' uses the workflow's current user. 'reference' uses a specific user ID. + """ + + update_properties: str + """ + A JSON string or Liquid template that evaluates to the properties to update on + the user. + """ + + recipient_gid: Optional[str] = None + """The global identifier (GID) of the user to update. + + Required when recipient_mode is 'reference'. Format: gid://Object/$users/{id} + """ + + +class WorkflowUpdateUserStep(BaseModel): + """An update user step. + + Updates properties of a specific user referenced in the workflow. + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Settings + """The settings for the update user step.""" + + type: Literal["update_user"] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_update_user_step_param.py b/src/knock_mapi/types/workflow_update_user_step_param.py new file mode 100644 index 00000000..803d8315 --- /dev/null +++ b/src/knock_mapi/types/workflow_update_user_step_param.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +from .condition_group_param import ConditionGroupParam + +__all__ = ["WorkflowUpdateUserStepParam", "Settings"] + + +class Settings(TypedDict, total=False): + """The settings for the update user step.""" + + recipient_mode: Required[Literal["current", "reference"]] + """The recipient mode determining how the user is selected. + + 'current' uses the workflow's current user. 'reference' uses a specific user ID. + """ + + update_properties: Required[str] + """ + A JSON string or Liquid template that evaluates to the properties to update on + the user. + """ + + recipient_gid: Optional[str] + """The global identifier (GID) of the user to update. + + Required when recipient_mode is 'reference'. Format: gid://Object/$users/{id} + """ + + +class WorkflowUpdateUserStepParam(TypedDict, total=False): + """An update user step. + + Updates properties of a specific user referenced in the workflow. + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + settings: Required[Settings] + """The settings for the update user step.""" + + type: Required[Literal["update_user"]] + """The type of the workflow step.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" diff --git a/src/knock_mapi/types/workflow_upsert_params.py b/src/knock_mapi/types/workflow_upsert_params.py index d4dc8db5..459c6e42 100644 --- a/src/knock_mapi/types/workflow_upsert_params.py +++ b/src/knock_mapi/types/workflow_upsert_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional +from typing import Dict, Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr from .condition_group_param import ConditionGroupParam __all__ = ["WorkflowUpsertParams", "Workflow", "WorkflowSettings"] @@ -20,6 +21,12 @@ class WorkflowUpsertParams(TypedDict, total=False): annotate: bool """Whether to annotate the resource. Only used in the Knock CLI.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + commit: bool """Whether to commit the resource at the same time as modifying it.""" @@ -28,6 +35,8 @@ class WorkflowUpsertParams(TypedDict, total=False): class WorkflowSettings(TypedDict, total=False): + """A map of workflow settings.""" + is_commercial: bool """Whether the workflow is commercial. Defaults to false.""" @@ -40,13 +49,15 @@ class WorkflowSettings(TypedDict, total=False): class Workflow(TypedDict, total=False): + """A workflow request for upserting a workflow.""" + name: Required[str] """A name for the workflow. Must be at maximum 255 characters in length.""" steps: Required[Iterable["WorkflowStepParam"]] """A list of workflow step objects in the workflow.""" - categories: List[str] + categories: SequenceNotStr[str] """ A list of [categories](https://docs.knock.app/concepts/workflows#workflow-categories) that @@ -67,9 +78,10 @@ class Workflow(TypedDict, total=False): """A map of workflow settings.""" trigger_data_json_schema: Dict[str, object] - """A JSON schema for the expected structure of the workflow trigger's data payload. - - Used to validate trigger requests. Read more in the + """ + A JSON schema for the expected structure of the workflow trigger's `data` + payload (available in templates as `{{ data.field_name }}`). Used to validate + trigger requests. Read more in the [docs](https://docs.knock.app/developer-tools/validating-trigger-data). """ diff --git a/src/knock_mapi/types/workflow_upsert_response.py b/src/knock_mapi/types/workflow_upsert_response.py index f7bea7c5..053ff9d2 100644 --- a/src/knock_mapi/types/workflow_upsert_response.py +++ b/src/knock_mapi/types/workflow_upsert_response.py @@ -2,20 +2,19 @@ from __future__ import annotations -from .._compat import PYDANTIC_V2 from .._models import BaseModel __all__ = ["WorkflowUpsertResponse"] class WorkflowUpsertResponse(BaseModel): + """Wraps the Workflow response under the `workflow` key.""" + workflow: "Workflow" - """A workflow object.""" + """A workflow object. + Read more in the [docs](https://docs.knock.app/concepts/workflows). + """ -from .workflow import Workflow -if PYDANTIC_V2: - WorkflowUpsertResponse.model_rebuild() -else: - WorkflowUpsertResponse.update_forward_refs() # type: ignore +from .workflow import Workflow diff --git a/src/knock_mapi/types/workflow_validate_params.py b/src/knock_mapi/types/workflow_validate_params.py index 896e83f9..faae34d5 100644 --- a/src/knock_mapi/types/workflow_validate_params.py +++ b/src/knock_mapi/types/workflow_validate_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional +from typing import Dict, Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr from .condition_group_param import ConditionGroupParam __all__ = ["WorkflowValidateParams", "Workflow", "WorkflowSettings"] @@ -17,8 +18,16 @@ class WorkflowValidateParams(TypedDict, total=False): workflow: Required[Workflow] """A workflow request for upserting a workflow.""" + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + class WorkflowSettings(TypedDict, total=False): + """A map of workflow settings.""" + is_commercial: bool """Whether the workflow is commercial. Defaults to false.""" @@ -31,13 +40,15 @@ class WorkflowSettings(TypedDict, total=False): class Workflow(TypedDict, total=False): + """A workflow request for upserting a workflow.""" + name: Required[str] """A name for the workflow. Must be at maximum 255 characters in length.""" steps: Required[Iterable["WorkflowStepParam"]] """A list of workflow step objects in the workflow.""" - categories: List[str] + categories: SequenceNotStr[str] """ A list of [categories](https://docs.knock.app/concepts/workflows#workflow-categories) that @@ -58,9 +69,10 @@ class Workflow(TypedDict, total=False): """A map of workflow settings.""" trigger_data_json_schema: Dict[str, object] - """A JSON schema for the expected structure of the workflow trigger's data payload. - - Used to validate trigger requests. Read more in the + """ + A JSON schema for the expected structure of the workflow trigger's `data` + payload (available in templates as `{{ data.field_name }}`). Used to validate + trigger requests. Read more in the [docs](https://docs.knock.app/developer-tools/validating-trigger-data). """ diff --git a/src/knock_mapi/types/workflow_validate_response.py b/src/knock_mapi/types/workflow_validate_response.py index f494e9ba..96b8e019 100644 --- a/src/knock_mapi/types/workflow_validate_response.py +++ b/src/knock_mapi/types/workflow_validate_response.py @@ -2,20 +2,19 @@ from __future__ import annotations -from .._compat import PYDANTIC_V2 from .._models import BaseModel __all__ = ["WorkflowValidateResponse"] class WorkflowValidateResponse(BaseModel): + """Wraps the Workflow response under the `workflow` key.""" + workflow: "Workflow" - """A workflow object.""" + """A workflow object. + Read more in the [docs](https://docs.knock.app/concepts/workflows). + """ -from .workflow import Workflow -if PYDANTIC_V2: - WorkflowValidateResponse.model_rebuild() -else: - WorkflowValidateResponse.update_forward_refs() # type: ignore +from .workflow import Workflow diff --git a/src/knock_mapi/types/workflow_webhook_step.py b/src/knock_mapi/types/workflow_webhook_step.py new file mode 100644 index 00000000..746f464a --- /dev/null +++ b/src/knock_mapi/types/workflow_webhook_step.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .send_window import SendWindow +from .condition_group import ConditionGroup +from .webhook_template import WebhookTemplate + +__all__ = ["WorkflowWebhookStep"] + + +class WorkflowWebhookStep(BaseModel): + """A webhook step within a workflow to send an HTTP request to a generic channel. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: str + """The reference key of the workflow step. Must be unique per workflow.""" + + template: WebhookTemplate + """A webhook template. + + By default, a webhook step will use the request settings you configured in your + webhook channel. You can override this as you see fit on a per-step basis. + """ + + type: Literal["channel"] + """The type of the workflow step.""" + + channel_group_key: Optional[str] = None + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] = None + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_type: Optional[Literal["http"]] = None + """The type of the channel step. Always `http` for webhook steps.""" + + conditions: Optional[ConditionGroup] = None + """A group of conditions to be evaluated.""" + + description: Optional[str] = None + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] = None + """A name for the workflow step.""" + + send_windows: Optional[List[SendWindow]] = None + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflow_webhook_step_param.py b/src/knock_mapi/types/workflow_webhook_step_param.py new file mode 100644 index 00000000..92c8fb18 --- /dev/null +++ b/src/knock_mapi/types/workflow_webhook_step_param.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .send_window_param import SendWindowParam +from .condition_group_param import ConditionGroupParam +from .webhook_template_param import WebhookTemplateParam + +__all__ = ["WorkflowWebhookStepParam"] + + +class WorkflowWebhookStepParam(TypedDict, total=False): + """A webhook step within a workflow to send an HTTP request to a generic channel. + + Read more in the [docs](https://docs.knock.app/designing-workflows/channel-step). + """ + + ref: Required[str] + """The reference key of the workflow step. Must be unique per workflow.""" + + template: Required[WebhookTemplateParam] + """A webhook template. + + By default, a webhook step will use the request settings you configured in your + webhook channel. You can override this as you see fit on a per-step basis. + """ + + type: Required[Literal["channel"]] + """The type of the workflow step.""" + + channel_group_key: Optional[str] + """ + The key of the channel group to which the channel step will be sending a + notification. Either `channel_key` or `channel_group_key` must be provided, but + not both. + """ + + channel_key: Optional[str] + """ + The key of a specific configured channel instance (e.g., 'knock-email', + 'postmark', 'sendgrid-marketing') to send the notification through. Either + `channel_key` or `channel_group_key` must be provided, but not both. + """ + + channel_type: Literal["http"] + """The type of the channel step. Always `http` for webhook steps.""" + + conditions: Optional[ConditionGroupParam] + """A group of conditions to be evaluated.""" + + description: Optional[str] + """An arbitrary string attached to a workflow step. + + Useful for adding notes about the workflow for internal purposes. + """ + + name: Optional[str] + """A name for the workflow step.""" + + send_windows: Optional[Iterable[SendWindowParam]] + """A list of send window objects. + + Must include one send window object per day of the week. + """ diff --git a/src/knock_mapi/types/workflows/step_preview_template_params.py b/src/knock_mapi/types/workflows/step_preview_template_params.py index c35479dc..70e958a0 100644 --- a/src/knock_mapi/types/workflows/step_preview_template_params.py +++ b/src/knock_mapi/types/workflows/step_preview_template_params.py @@ -5,7 +5,13 @@ from typing import Dict, Union, Optional from typing_extensions import Required, TypeAlias, TypedDict -__all__ = ["StepPreviewTemplateParams", "Recipient", "RecipientUnionMember1", "Actor", "ActorUnionMember1"] +__all__ = [ + "StepPreviewTemplateParams", + "Recipient", + "RecipientObjectRecipientReference", + "Actor", + "ActorObjectRecipientReference", +] class StepPreviewTemplateParams(TypedDict, total=False): @@ -20,6 +26,12 @@ class StepPreviewTemplateParams(TypedDict, total=False): a user), or by a reference for an object. """ + branch: str + """The slug of a branch to use. + + This option can only be used when `environment` is `"development"`. + """ + actor: Optional[Actor] """ A recipient reference, used when referencing a recipient by either their ID (for @@ -30,22 +42,30 @@ class StepPreviewTemplateParams(TypedDict, total=False): """The data to pass to the workflow template for rendering.""" tenant: Optional[str] - """The tenant to associate the workflow with.""" + """The tenant to associate the workflow with. Must not contain whitespace.""" -class RecipientUnionMember1(TypedDict, total=False): +class RecipientObjectRecipientReference(TypedDict, total=False): + """An object reference.""" + id: Required[str] + """The ID of the object.""" collection: Required[str] + """The collection of the object.""" + +Recipient: TypeAlias = Union[str, RecipientObjectRecipientReference] -Recipient: TypeAlias = Union[str, RecipientUnionMember1] +class ActorObjectRecipientReference(TypedDict, total=False): + """An object reference.""" -class ActorUnionMember1(TypedDict, total=False): id: Required[str] + """The ID of the object.""" collection: Required[str] + """The collection of the object.""" -Actor: TypeAlias = Union[str, ActorUnionMember1] +Actor: TypeAlias = Union[str, ActorObjectRecipientReference] diff --git a/src/knock_mapi/types/workflows/step_preview_template_response.py b/src/knock_mapi/types/workflows/step_preview_template_response.py index 83939bc4..c79a426b 100644 --- a/src/knock_mapi/types/workflows/step_preview_template_response.py +++ b/src/knock_mapi/types/workflows/step_preview_template_response.py @@ -17,6 +17,8 @@ class StepPreviewTemplateResponse(BaseModel): + """A response to a preview workflow template request.""" + content_type: Literal["email", "in_app_feed", "push", "chat", "sms", "http"] """The content type of the preview.""" diff --git a/tests/api_resources/test_api_keys.py b/tests/api_resources/test_api_keys.py index bf8cf398..f2c73ab9 100644 --- a/tests/api_resources/test_api_keys.py +++ b/tests/api_resources/test_api_keys.py @@ -17,9 +17,7 @@ class TestAPIKeys: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_exchange(self, client: KnockMgmt) -> None: api_key = client.api_keys.exchange( @@ -27,9 +25,7 @@ def test_method_exchange(self, client: KnockMgmt) -> None: ) assert_matches_type(APIKeyExchangeResponse, api_key, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_exchange(self, client: KnockMgmt) -> None: response = client.api_keys.with_raw_response.exchange( @@ -41,9 +37,7 @@ def test_raw_response_exchange(self, client: KnockMgmt) -> None: api_key = response.parse() assert_matches_type(APIKeyExchangeResponse, api_key, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_exchange(self, client: KnockMgmt) -> None: with client.api_keys.with_streaming_response.exchange( @@ -59,11 +53,11 @@ def test_streaming_response_exchange(self, client: KnockMgmt) -> None: class TestAsyncAPIKeys: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_exchange(self, async_client: AsyncKnockMgmt) -> None: api_key = await async_client.api_keys.exchange( @@ -71,9 +65,7 @@ async def test_method_exchange(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(APIKeyExchangeResponse, api_key, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_exchange(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.api_keys.with_raw_response.exchange( @@ -85,9 +77,7 @@ async def test_raw_response_exchange(self, async_client: AsyncKnockMgmt) -> None api_key = await response.parse() assert_matches_type(APIKeyExchangeResponse, api_key, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_exchange(self, async_client: AsyncKnockMgmt) -> None: async with async_client.api_keys.with_streaming_response.exchange( diff --git a/tests/api_resources/test_audiences.py b/tests/api_resources/test_audiences.py new file mode 100644 index 00000000..85234874 --- /dev/null +++ b/tests/api_resources/test_audiences.py @@ -0,0 +1,692 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from knock_mapi import KnockMgmt, AsyncKnockMgmt +from tests.utils import assert_matches_type +from knock_mapi.types import ( + Audience, + AudienceUpsertResponse, + AudienceArchiveResponse, + AudienceValidateResponse, +) +from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAudiences: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + audience = client.audiences.retrieve( + audience_key="audience_key", + environment="development", + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: + audience = client.audiences.retrieve( + audience_key="audience_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.audiences.with_raw_response.retrieve( + audience_key="audience_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.audiences.with_streaming_response.retrieve( + audience_key="audience_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + client.audiences.with_raw_response.retrieve( + audience_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: KnockMgmt) -> None: + audience = client.audiences.list( + environment="development", + ) + assert_matches_type(SyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: KnockMgmt) -> None: + audience = client.audiences.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(SyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: KnockMgmt) -> None: + response = client.audiences.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(SyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: KnockMgmt) -> None: + with client.audiences.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(SyncEntriesCursor[Audience], audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_archive(self, client: KnockMgmt) -> None: + audience = client.audiences.archive( + audience_key="audience_key", + environment="development", + ) + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_archive(self, client: KnockMgmt) -> None: + response = client.audiences.with_raw_response.archive( + audience_key="audience_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_archive(self, client: KnockMgmt) -> None: + with client.audiences.with_streaming_response.archive( + audience_key="audience_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_archive(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + client.audiences.with_raw_response.archive( + audience_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert(self, client: KnockMgmt) -> None: + audience = client.audiences.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: + audience = client.audiences.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + "description": "Users on the premium plan", + "segments": [ + { + "conditions": [ + { + "operator": "equal_to", + "property": "recipient.plan", + "argument": "premium", + } + ] + } + ], + }, + annotate=True, + branch="feature-branch", + commit=True, + commit_message="commit_message", + ) + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_upsert(self, client: KnockMgmt) -> None: + response = client.audiences.with_raw_response.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_upsert(self, client: KnockMgmt) -> None: + with client.audiences.with_streaming_response.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_upsert(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + client.audiences.with_raw_response.upsert( + audience_key="", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate(self, client: KnockMgmt) -> None: + audience = client.audiences.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: + audience = client.audiences.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + "description": "Users on the premium plan", + "segments": [ + { + "conditions": [ + { + "operator": "equal_to", + "property": "recipient.plan", + "argument": "premium", + } + ] + } + ], + }, + branch="feature-branch", + ) + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_validate(self, client: KnockMgmt) -> None: + response = client.audiences.with_raw_response.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = response.parse() + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_validate(self, client: KnockMgmt) -> None: + with client.audiences.with_streaming_response.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = response.parse() + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_validate(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + client.audiences.with_raw_response.validate( + audience_key="", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + +class TestAsyncAudiences: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.retrieve( + audience_key="audience_key", + environment="development", + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.retrieve( + audience_key="audience_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.audiences.with_raw_response.retrieve( + audience_key="audience_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.audiences.with_streaming_response.retrieve( + audience_key="audience_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(Audience, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + await async_client.audiences.with_raw_response.retrieve( + audience_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.list( + environment="development", + ) + assert_matches_type(AsyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(AsyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.audiences.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AsyncEntriesCursor[Audience], audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.audiences.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AsyncEntriesCursor[Audience], audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_archive(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.archive( + audience_key="audience_key", + environment="development", + ) + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_archive(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.audiences.with_raw_response.archive( + audience_key="audience_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_archive(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.audiences.with_streaming_response.archive( + audience_key="audience_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AudienceArchiveResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_archive(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + await async_client.audiences.with_raw_response.archive( + audience_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + "description": "Users on the premium plan", + "segments": [ + { + "conditions": [ + { + "operator": "equal_to", + "property": "recipient.plan", + "argument": "premium", + } + ] + } + ], + }, + annotate=True, + branch="feature-branch", + commit=True, + commit_message="commit_message", + ) + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.audiences.with_raw_response.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.audiences.with_streaming_response.upsert( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AudienceUpsertResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + await async_client.audiences.with_raw_response.upsert( + audience_key="", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + audience = await async_client.audiences.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + "description": "Users on the premium plan", + "segments": [ + { + "conditions": [ + { + "operator": "equal_to", + "property": "recipient.plan", + "argument": "premium", + } + ] + } + ], + }, + branch="feature-branch", + ) + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.audiences.with_raw_response.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + audience = await response.parse() + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.audiences.with_streaming_response.validate( + audience_key="audience_key", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + audience = await response.parse() + assert_matches_type(AudienceValidateResponse, audience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `audience_key` but received ''"): + await async_client.audiences.with_raw_response.validate( + audience_key="", + environment="development", + audience={ + "name": "Premium users", + "type": "dynamic", + }, + ) diff --git a/tests/api_resources/test_auth.py b/tests/api_resources/test_auth.py index 318b4c2b..f169b31a 100644 --- a/tests/api_resources/test_auth.py +++ b/tests/api_resources/test_auth.py @@ -17,17 +17,13 @@ class TestAuth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_verify(self, client: KnockMgmt) -> None: auth = client.auth.verify() assert_matches_type(AuthVerifyResponse, auth, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_verify(self, client: KnockMgmt) -> None: response = client.auth.with_raw_response.verify() @@ -37,9 +33,7 @@ def test_raw_response_verify(self, client: KnockMgmt) -> None: auth = response.parse() assert_matches_type(AuthVerifyResponse, auth, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_verify(self, client: KnockMgmt) -> None: with client.auth.with_streaming_response.verify() as response: @@ -53,19 +47,17 @@ def test_streaming_response_verify(self, client: KnockMgmt) -> None: class TestAsyncAuth: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_verify(self, async_client: AsyncKnockMgmt) -> None: auth = await async_client.auth.verify() assert_matches_type(AuthVerifyResponse, auth, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_verify(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.auth.with_raw_response.verify() @@ -75,9 +67,7 @@ async def test_raw_response_verify(self, async_client: AsyncKnockMgmt) -> None: auth = await response.parse() assert_matches_type(AuthVerifyResponse, auth, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_verify(self, async_client: AsyncKnockMgmt) -> None: async with async_client.auth.with_streaming_response.verify() as response: diff --git a/tests/api_resources/test_branches.py b/tests/api_resources/test_branches.py new file mode 100644 index 00000000..a146834a --- /dev/null +++ b/tests/api_resources/test_branches.py @@ -0,0 +1,393 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from knock_mapi import KnockMgmt, AsyncKnockMgmt +from tests.utils import assert_matches_type +from knock_mapi.types import ( + Branch, +) +from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBranches: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: KnockMgmt) -> None: + branch = client.branches.create( + branch_slug="feature-branch", + environment="development", + ) + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: KnockMgmt) -> None: + response = client.branches.with_raw_response.create( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: KnockMgmt) -> None: + with client.branches.with_streaming_response.create( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_create(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + client.branches.with_raw_response.create( + branch_slug="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + branch = client.branches.retrieve( + branch_slug="feature-branch", + environment="development", + ) + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.branches.with_raw_response.retrieve( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.branches.with_streaming_response.retrieve( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + client.branches.with_raw_response.retrieve( + branch_slug="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: KnockMgmt) -> None: + branch = client.branches.list( + environment="development", + ) + assert_matches_type(SyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: KnockMgmt) -> None: + branch = client.branches.list( + environment="development", + after="after", + before="before", + limit=0, + ) + assert_matches_type(SyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: KnockMgmt) -> None: + response = client.branches.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = response.parse() + assert_matches_type(SyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: KnockMgmt) -> None: + with client.branches.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = response.parse() + assert_matches_type(SyncEntriesCursor[Branch], branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: KnockMgmt) -> None: + branch = client.branches.delete( + branch_slug="feature-branch", + environment="development", + ) + assert branch is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: KnockMgmt) -> None: + response = client.branches.with_raw_response.delete( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = response.parse() + assert branch is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: KnockMgmt) -> None: + with client.branches.with_streaming_response.delete( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = response.parse() + assert branch is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + client.branches.with_raw_response.delete( + branch_slug="", + environment="development", + ) + + +class TestAsyncBranches: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncKnockMgmt) -> None: + branch = await async_client.branches.create( + branch_slug="feature-branch", + environment="development", + ) + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.branches.with_raw_response.create( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = await response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.branches.with_streaming_response.create( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = await response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_create(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + await async_client.branches.with_raw_response.create( + branch_slug="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + branch = await async_client.branches.retrieve( + branch_slug="feature-branch", + environment="development", + ) + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.branches.with_raw_response.retrieve( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = await response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.branches.with_streaming_response.retrieve( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = await response.parse() + assert_matches_type(Branch, branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + await async_client.branches.with_raw_response.retrieve( + branch_slug="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: + branch = await async_client.branches.list( + environment="development", + ) + assert_matches_type(AsyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + branch = await async_client.branches.list( + environment="development", + after="after", + before="before", + limit=0, + ) + assert_matches_type(AsyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.branches.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = await response.parse() + assert_matches_type(AsyncEntriesCursor[Branch], branch, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.branches.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = await response.parse() + assert_matches_type(AsyncEntriesCursor[Branch], branch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncKnockMgmt) -> None: + branch = await async_client.branches.delete( + branch_slug="feature-branch", + environment="development", + ) + assert branch is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.branches.with_raw_response.delete( + branch_slug="feature-branch", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + branch = await response.parse() + assert branch is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.branches.with_streaming_response.delete( + branch_slug="feature-branch", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + branch = await response.parse() + assert branch is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `branch_slug` but received ''"): + await async_client.branches.with_raw_response.delete( + branch_slug="", + environment="development", + ) diff --git a/tests/api_resources/test_broadcasts.py b/tests/api_resources/test_broadcasts.py new file mode 100644 index 00000000..f86b1d4d --- /dev/null +++ b/tests/api_resources/test_broadcasts.py @@ -0,0 +1,1056 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from knock_mapi import KnockMgmt, AsyncKnockMgmt +from tests.utils import assert_matches_type +from knock_mapi.types import ( + Broadcast, + BroadcastSendResponse, + BroadcastCancelResponse, + BroadcastUpsertResponse, + BroadcastValidateResponse, +) +from knock_mapi._utils import parse_datetime +from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBroadcasts: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.retrieve( + broadcast_key="broadcast_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(Broadcast, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + client.broadcasts.with_raw_response.retrieve( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.list( + environment="development", + ) + assert_matches_type(SyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(SyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(SyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(SyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_cancel(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.cancel( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_cancel_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.cancel( + broadcast_key="broadcast_key", + environment="development", + branch="feature-branch", + ) + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_cancel(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.cancel( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_cancel(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.cancel( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_cancel(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + client.broadcasts.with_raw_response.cancel( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_send(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.send( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_send_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.send( + broadcast_key="broadcast_key", + environment="development", + branch="feature-branch", + send_at=parse_datetime("2024-03-20T10:00:00Z"), + ) + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_send(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.send( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_send(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.send( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_send(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + client.broadcasts.with_raw_response.send( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": { + "markdown_body": "Hello **{{ recipient.name }}**", + "action_buttons": [ + { + "action": "https://example.com", + "label": "Button 1", + } + ], + "action_url": "{{ vars.app_url }}", + }, + "type": "channel", + "channel_group_key": None, + "channel_key": "in-app-feed", + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", + "conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + "description": "This is a description of the channel step", + "name": "Channel 1", + "send_windows": [ + { + "day": "monday", + "type": "send", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", + } + ], + } + ], + "categories": ["announcement"], + "description": "A broadcast to all users", + "scheduled_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "settings": { + "is_commercial": True, + "override_preferences": False, + }, + "target_audience_key": "all-users", + }, + annotate=True, + branch="feature-branch", + ) + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_upsert(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_upsert(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_upsert(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + client.broadcasts.with_raw_response.upsert( + broadcast_key="", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: + broadcast = client.broadcasts.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": { + "markdown_body": "Hello **{{ recipient.name }}**", + "action_buttons": [ + { + "action": "https://example.com", + "label": "Button 1", + } + ], + "action_url": "{{ vars.app_url }}", + }, + "type": "channel", + "channel_group_key": None, + "channel_key": "in-app-feed", + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", + "conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + "description": "This is a description of the channel step", + "name": "Channel 1", + "send_windows": [ + { + "day": "monday", + "type": "send", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", + } + ], + } + ], + "categories": ["announcement"], + "description": "A broadcast to all users", + "scheduled_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "settings": { + "is_commercial": True, + "override_preferences": False, + }, + "target_audience_key": "all-users", + }, + branch="feature-branch", + ) + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_validate(self, client: KnockMgmt) -> None: + response = client.broadcasts.with_raw_response.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = response.parse() + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_validate(self, client: KnockMgmt) -> None: + with client.broadcasts.with_streaming_response.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = response.parse() + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_validate(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + client.broadcasts.with_raw_response.validate( + broadcast_key="", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + +class TestAsyncBroadcasts: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.retrieve( + broadcast_key="broadcast_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(Broadcast, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.retrieve( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(Broadcast, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + await async_client.broadcasts.with_raw_response.retrieve( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.list( + environment="development", + ) + assert_matches_type(AsyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(AsyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(AsyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(AsyncEntriesCursor[Broadcast], broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_cancel(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.cancel( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_cancel_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.cancel( + broadcast_key="broadcast_key", + environment="development", + branch="feature-branch", + ) + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.cancel( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.cancel( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(BroadcastCancelResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_cancel(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + await async_client.broadcasts.with_raw_response.cancel( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_send(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.send( + broadcast_key="broadcast_key", + environment="development", + ) + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_send_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.send( + broadcast_key="broadcast_key", + environment="development", + branch="feature-branch", + send_at=parse_datetime("2024-03-20T10:00:00Z"), + ) + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_send(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.send( + broadcast_key="broadcast_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_send(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.send( + broadcast_key="broadcast_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(BroadcastSendResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_send(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + await async_client.broadcasts.with_raw_response.send( + broadcast_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": { + "markdown_body": "Hello **{{ recipient.name }}**", + "action_buttons": [ + { + "action": "https://example.com", + "label": "Button 1", + } + ], + "action_url": "{{ vars.app_url }}", + }, + "type": "channel", + "channel_group_key": None, + "channel_key": "in-app-feed", + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", + "conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + "description": "This is a description of the channel step", + "name": "Channel 1", + "send_windows": [ + { + "day": "monday", + "type": "send", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", + } + ], + } + ], + "categories": ["announcement"], + "description": "A broadcast to all users", + "scheduled_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "settings": { + "is_commercial": True, + "override_preferences": False, + }, + "target_audience_key": "all-users", + }, + annotate=True, + branch="feature-branch", + ) + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.upsert( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(BroadcastUpsertResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + await async_client.broadcasts.with_raw_response.upsert( + broadcast_key="", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + broadcast = await async_client.broadcasts.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": { + "markdown_body": "Hello **{{ recipient.name }}**", + "action_buttons": [ + { + "action": "https://example.com", + "label": "Button 1", + } + ], + "action_url": "{{ vars.app_url }}", + }, + "type": "channel", + "channel_group_key": None, + "channel_key": "in-app-feed", + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", + "conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + "description": "This is a description of the channel step", + "name": "Channel 1", + "send_windows": [ + { + "day": "monday", + "type": "send", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", + } + ], + } + ], + "categories": ["announcement"], + "description": "A broadcast to all users", + "scheduled_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "settings": { + "is_commercial": True, + "override_preferences": False, + }, + "target_audience_key": "all-users", + }, + branch="feature-branch", + ) + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.broadcasts.with_raw_response.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + broadcast = await response.parse() + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.broadcasts.with_streaming_response.validate( + broadcast_key="broadcast_key", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + broadcast = await response.parse() + assert_matches_type(BroadcastValidateResponse, broadcast, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_key` but received ''"): + await async_client.broadcasts.with_raw_response.validate( + broadcast_key="", + environment="development", + broadcast={ + "name": "My Broadcast", + "steps": [ + { + "ref": "channel_1", + "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, + "type": "channel", + } + ], + }, + ) diff --git a/tests/api_resources/test_channel_groups.py b/tests/api_resources/test_channel_groups.py index 7a32416e..aeffa119 100644 --- a/tests/api_resources/test_channel_groups.py +++ b/tests/api_resources/test_channel_groups.py @@ -9,7 +9,10 @@ from knock_mapi import KnockMgmt, AsyncKnockMgmt from tests.utils import assert_matches_type -from knock_mapi.types import ChannelGroup +from knock_mapi.types import ( + ChannelGroup, + ChannelGroupUpsertResponse, +) from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,17 +21,55 @@ class TestChannelGroups: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + channel_group = client.channel_groups.retrieve( + "channel_group_key", + ) + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.channel_groups.with_raw_response.retrieve( + "channel_group_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = response.parse() + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.channel_groups.with_streaming_response.retrieve( + "channel_group_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel_group = response.parse() + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + client.channel_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: channel_group = client.channel_groups.list() assert_matches_type(SyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: channel_group = client.channel_groups.list( @@ -38,9 +79,7 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.channel_groups.with_raw_response.list() @@ -50,9 +89,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: channel_group = response.parse() assert_matches_type(SyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.channel_groups.with_streaming_response.list() as response: @@ -64,21 +101,184 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: KnockMgmt) -> None: + channel_group = client.channel_groups.delete( + "channel_group_key", + ) + assert channel_group is None -class TestAsyncChannelGroups: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: KnockMgmt) -> None: + response = client.channel_groups.with_raw_response.delete( + "channel_group_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = response.parse() + assert channel_group is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: KnockMgmt) -> None: + with client.channel_groups.with_streaming_response.delete( + "channel_group_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel_group = response.parse() + assert channel_group is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + client.channel_groups.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert(self, client: KnockMgmt) -> None: + channel_group = client.channel_groups.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: + channel_group = client.channel_groups.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + "channel_rules": [ + { + "channel_key": "push-fcm", + "rule_type": "always", + "argument": "argument", + "index": 0, + "operator": "equal_to", + "variable": "variable", + } + ], + "operator": "any", + }, + ) + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_upsert(self, client: KnockMgmt) -> None: + response = client.channel_groups.with_raw_response.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = response.parse() + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_upsert(self, client: KnockMgmt) -> None: + with client.channel_groups.with_streaming_response.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + channel_group = response.parse() + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_upsert(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + client.channel_groups.with_raw_response.upsert( + channel_group_key="", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + + +class TestAsyncChannelGroups: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + channel_group = await async_client.channel_groups.retrieve( + "channel_group_key", + ) + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.channel_groups.with_raw_response.retrieve( + "channel_group_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = await response.parse() + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.channel_groups.with_streaming_response.retrieve( + "channel_group_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel_group = await response.parse() + assert_matches_type(ChannelGroup, channel_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + await async_client.channel_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: channel_group = await async_client.channel_groups.list() assert_matches_type(AsyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: channel_group = await async_client.channel_groups.list( @@ -88,9 +288,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - ) assert_matches_type(AsyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.channel_groups.with_raw_response.list() @@ -100,9 +298,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: channel_group = await response.parse() assert_matches_type(AsyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.channel_groups.with_streaming_response.list() as response: @@ -113,3 +309,126 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert_matches_type(AsyncEntriesCursor[ChannelGroup], channel_group, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncKnockMgmt) -> None: + channel_group = await async_client.channel_groups.delete( + "channel_group_key", + ) + assert channel_group is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.channel_groups.with_raw_response.delete( + "channel_group_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = await response.parse() + assert channel_group is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.channel_groups.with_streaming_response.delete( + "channel_group_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel_group = await response.parse() + assert channel_group is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + await async_client.channel_groups.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: + channel_group = await async_client.channel_groups.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + channel_group = await async_client.channel_groups.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + "channel_rules": [ + { + "channel_key": "push-fcm", + "rule_type": "always", + "argument": "argument", + "index": 0, + "operator": "equal_to", + "variable": "variable", + } + ], + "operator": "any", + }, + ) + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.channel_groups.with_raw_response.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel_group = await response.parse() + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.channel_groups.with_streaming_response.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel_group = await response.parse() + assert_matches_type(ChannelGroupUpsertResponse, channel_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_group_key` but received ''"): + await async_client.channel_groups.with_raw_response.upsert( + channel_group_key="", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) diff --git a/tests/api_resources/test_channels.py b/tests/api_resources/test_channels.py index 3c227784..027bcdb7 100644 --- a/tests/api_resources/test_channels.py +++ b/tests/api_resources/test_channels.py @@ -18,29 +18,67 @@ class TestChannels: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + channel = client.channels.retrieve( + "channel_key", + ) + assert_matches_type(Channel, channel, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.channels.with_raw_response.retrieve( + "channel_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel = response.parse() + assert_matches_type(Channel, channel, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.channels.with_streaming_response.retrieve( + "channel_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel = response.parse() + assert_matches_type(Channel, channel, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_key` but received ''"): + client.channels.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: channel = client.channels.list() assert_matches_type(SyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: channel = client.channels.list( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", after="after", before="before", + include=["environment_settings"], limit=0, ) assert_matches_type(SyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.channels.with_raw_response.list() @@ -50,9 +88,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: channel = response.parse() assert_matches_type(SyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.channels.with_streaming_response.list() as response: @@ -66,31 +102,71 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: class TestAsyncChannels: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + channel = await async_client.channels.retrieve( + "channel_key", + ) + assert_matches_type(Channel, channel, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.channels.with_raw_response.retrieve( + "channel_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + channel = await response.parse() + assert_matches_type(Channel, channel, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.channels.with_streaming_response.retrieve( + "channel_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + channel = await response.parse() + assert_matches_type(Channel, channel, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `channel_key` but received ''"): + await async_client.channels.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: channel = await async_client.channels.list() assert_matches_type(AsyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: channel = await async_client.channels.list( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", after="after", before="before", + include=["environment_settings"], limit=0, ) assert_matches_type(AsyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.channels.with_raw_response.list() @@ -100,9 +176,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: channel = await response.parse() assert_matches_type(AsyncEntriesCursor[Channel], channel, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.channels.with_streaming_response.list() as response: diff --git a/tests/api_resources/test_commits.py b/tests/api_resources/test_commits.py index 368fb3a8..fd47f024 100644 --- a/tests/api_resources/test_commits.py +++ b/tests/api_resources/test_commits.py @@ -23,9 +23,7 @@ class TestCommits: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: commit = client.commits.retrieve( @@ -33,9 +31,7 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(Commit, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.commits.with_raw_response.retrieve( @@ -47,9 +43,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: commit = response.parse() assert_matches_type(Commit, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.commits.with_streaming_response.retrieve( @@ -63,9 +57,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): @@ -73,9 +65,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: "", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: commit = client.commits.list( @@ -83,23 +73,22 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: commit = client.commits.list( environment="development", after="after", before="before", + branch="feature-branch", limit=0, promoted=True, + resource_id="resource_id", + resource_type="audience", ) assert_matches_type(SyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.commits.with_raw_response.list( @@ -111,9 +100,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: commit = response.parse() assert_matches_type(SyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.commits.with_streaming_response.list( @@ -127,9 +114,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_commit_all(self, client: KnockMgmt) -> None: commit = client.commits.commit_all( @@ -137,20 +122,19 @@ def test_method_commit_all(self, client: KnockMgmt) -> None: ) assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_commit_all_with_all_params(self, client: KnockMgmt) -> None: commit = client.commits.commit_all( environment="development", + branch="feature-branch", commit_message="commit_message", + resource_id="resource_id", + resource_type="audience", ) assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_commit_all(self, client: KnockMgmt) -> None: response = client.commits.with_raw_response.commit_all( @@ -162,9 +146,7 @@ def test_raw_response_commit_all(self, client: KnockMgmt) -> None: commit = response.parse() assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_commit_all(self, client: KnockMgmt) -> None: with client.commits.with_streaming_response.commit_all( @@ -178,9 +160,7 @@ def test_streaming_response_commit_all(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_promote_all(self, client: KnockMgmt) -> None: commit = client.commits.promote_all( @@ -188,9 +168,18 @@ def test_method_promote_all(self, client: KnockMgmt) -> None: ) assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_promote_all_with_all_params(self, client: KnockMgmt) -> None: + commit = client.commits.promote_all( + to_environment="to_environment", + branch="branch", + resource_id="resource_id", + resource_type="audience", + ) + assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_promote_all(self, client: KnockMgmt) -> None: response = client.commits.with_raw_response.promote_all( @@ -202,9 +191,7 @@ def test_raw_response_promote_all(self, client: KnockMgmt) -> None: commit = response.parse() assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_promote_all(self, client: KnockMgmt) -> None: with client.commits.with_streaming_response.promote_all( @@ -218,9 +205,7 @@ def test_streaming_response_promote_all(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_promote_one(self, client: KnockMgmt) -> None: commit = client.commits.promote_one( @@ -228,9 +213,7 @@ def test_method_promote_one(self, client: KnockMgmt) -> None: ) assert_matches_type(CommitPromoteOneResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_promote_one(self, client: KnockMgmt) -> None: response = client.commits.with_raw_response.promote_one( @@ -242,9 +225,7 @@ def test_raw_response_promote_one(self, client: KnockMgmt) -> None: commit = response.parse() assert_matches_type(CommitPromoteOneResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_promote_one(self, client: KnockMgmt) -> None: with client.commits.with_streaming_response.promote_one( @@ -258,9 +239,7 @@ def test_streaming_response_promote_one(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_promote_one(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): @@ -270,11 +249,11 @@ def test_path_params_promote_one(self, client: KnockMgmt) -> None: class TestAsyncCommits: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.retrieve( @@ -282,9 +261,7 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(Commit, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.commits.with_raw_response.retrieve( @@ -296,9 +273,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None commit = await response.parse() assert_matches_type(Commit, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.commits.with_streaming_response.retrieve( @@ -312,9 +287,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): @@ -322,9 +295,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: "", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.list( @@ -332,23 +303,22 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.list( environment="development", after="after", before="before", + branch="feature-branch", limit=0, promoted=True, + resource_id="resource_id", + resource_type="audience", ) assert_matches_type(AsyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.commits.with_raw_response.list( @@ -360,9 +330,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: commit = await response.parse() assert_matches_type(AsyncEntriesCursor[Commit], commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.commits.with_streaming_response.list( @@ -376,9 +344,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_commit_all(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.commit_all( @@ -386,20 +352,19 @@ async def test_method_commit_all(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_commit_all_with_all_params(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.commit_all( environment="development", + branch="feature-branch", commit_message="commit_message", + resource_id="resource_id", + resource_type="audience", ) assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_commit_all(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.commits.with_raw_response.commit_all( @@ -411,9 +376,7 @@ async def test_raw_response_commit_all(self, async_client: AsyncKnockMgmt) -> No commit = await response.parse() assert_matches_type(CommitCommitAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_commit_all(self, async_client: AsyncKnockMgmt) -> None: async with async_client.commits.with_streaming_response.commit_all( @@ -427,9 +390,7 @@ async def test_streaming_response_commit_all(self, async_client: AsyncKnockMgmt) assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_promote_all(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.promote_all( @@ -437,9 +398,18 @@ async def test_method_promote_all(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_promote_all_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + commit = await async_client.commits.promote_all( + to_environment="to_environment", + branch="branch", + resource_id="resource_id", + resource_type="audience", + ) + assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_promote_all(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.commits.with_raw_response.promote_all( @@ -451,9 +421,7 @@ async def test_raw_response_promote_all(self, async_client: AsyncKnockMgmt) -> N commit = await response.parse() assert_matches_type(CommitPromoteAllResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_promote_all(self, async_client: AsyncKnockMgmt) -> None: async with async_client.commits.with_streaming_response.promote_all( @@ -467,9 +435,7 @@ async def test_streaming_response_promote_all(self, async_client: AsyncKnockMgmt assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_promote_one(self, async_client: AsyncKnockMgmt) -> None: commit = await async_client.commits.promote_one( @@ -477,9 +443,7 @@ async def test_method_promote_one(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(CommitPromoteOneResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_promote_one(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.commits.with_raw_response.promote_one( @@ -491,9 +455,7 @@ async def test_raw_response_promote_one(self, async_client: AsyncKnockMgmt) -> N commit = await response.parse() assert_matches_type(CommitPromoteOneResponse, commit, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_promote_one(self, async_client: AsyncKnockMgmt) -> None: async with async_client.commits.with_streaming_response.promote_one( @@ -507,9 +469,7 @@ async def test_streaming_response_promote_one(self, async_client: AsyncKnockMgmt assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_promote_one(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): diff --git a/tests/api_resources/test_email_layouts.py b/tests/api_resources/test_email_layouts.py index f9c92b17..5b94457e 100644 --- a/tests/api_resources/test_email_layouts.py +++ b/tests/api_resources/test_email_layouts.py @@ -22,9 +22,7 @@ class TestEmailLayouts: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.retrieve( @@ -33,22 +31,19 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.retrieve( email_layout_key="email_layout_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.email_layouts.with_raw_response.retrieve( @@ -61,9 +56,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: email_layout = response.parse() assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.email_layouts.with_streaming_response.retrieve( @@ -78,9 +71,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): @@ -89,9 +80,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.list( @@ -99,9 +88,7 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.list( @@ -109,14 +96,13 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(SyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.email_layouts.with_raw_response.list( @@ -128,9 +114,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: email_layout = response.parse() assert_matches_type(SyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.email_layouts.with_streaming_response.list( @@ -144,9 +128,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.upsert( @@ -160,9 +142,7 @@ def test_method_upsert(self, client: KnockMgmt) -> None: ) assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.upsert( @@ -178,16 +158,16 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "url": "http://example.com", } ], + "is_mjml": True, }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_upsert(self, client: KnockMgmt) -> None: response = client.email_layouts.with_raw_response.upsert( @@ -205,9 +185,7 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: email_layout = response.parse() assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_upsert(self, client: KnockMgmt) -> None: with client.email_layouts.with_streaming_response.upsert( @@ -227,9 +205,7 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_upsert(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): @@ -243,9 +219,7 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.validate( @@ -259,9 +233,7 @@ def test_method_validate(self, client: KnockMgmt) -> None: ) assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: email_layout = client.email_layouts.validate( @@ -277,13 +249,13 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "url": "http://example.com", } ], + "is_mjml": True, }, + branch="feature-branch", ) assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_validate(self, client: KnockMgmt) -> None: response = client.email_layouts.with_raw_response.validate( @@ -301,9 +273,7 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: email_layout = response.parse() assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_validate(self, client: KnockMgmt) -> None: with client.email_layouts.with_streaming_response.validate( @@ -323,9 +293,7 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_validate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): @@ -341,11 +309,11 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: class TestAsyncEmailLayouts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.retrieve( @@ -354,22 +322,19 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.retrieve( email_layout_key="email_layout_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.email_layouts.with_raw_response.retrieve( @@ -382,9 +347,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None email_layout = await response.parse() assert_matches_type(EmailLayout, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.email_layouts.with_streaming_response.retrieve( @@ -399,9 +362,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): @@ -410,9 +371,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.list( @@ -420,9 +379,7 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.list( @@ -430,14 +387,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(AsyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.email_layouts.with_raw_response.list( @@ -449,9 +405,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: email_layout = await response.parse() assert_matches_type(AsyncEntriesCursor[EmailLayout], email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.email_layouts.with_streaming_response.list( @@ -465,9 +419,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.upsert( @@ -481,9 +433,7 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.upsert( @@ -499,16 +449,16 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "url": "http://example.com", } ], + "is_mjml": True, }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.email_layouts.with_raw_response.upsert( @@ -526,9 +476,7 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: email_layout = await response.parse() assert_matches_type(EmailLayoutUpsertResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: async with async_client.email_layouts.with_streaming_response.upsert( @@ -548,9 +496,7 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): @@ -564,9 +510,7 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.validate( @@ -580,9 +524,7 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: email_layout = await async_client.email_layouts.validate( @@ -598,13 +540,13 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "url": "http://example.com", } ], + "is_mjml": True, }, + branch="feature-branch", ) assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.email_layouts.with_raw_response.validate( @@ -622,9 +564,7 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None email_layout = await response.parse() assert_matches_type(EmailLayoutValidateResponse, email_layout, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.email_layouts.with_streaming_response.validate( @@ -644,9 +584,7 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_layout_key` but received ''"): diff --git a/tests/api_resources/test_environments.py b/tests/api_resources/test_environments.py index 560f10b6..07aee84d 100644 --- a/tests/api_resources/test_environments.py +++ b/tests/api_resources/test_environments.py @@ -18,9 +18,7 @@ class TestEnvironments: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: environment = client.environments.retrieve( @@ -28,9 +26,7 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(Environment, environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.environments.with_raw_response.retrieve( @@ -42,9 +38,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: environment = response.parse() assert_matches_type(Environment, environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.environments.with_streaming_response.retrieve( @@ -58,9 +52,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `environment_slug` but received ''"): @@ -68,17 +60,13 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: "", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: environment = client.environments.list() assert_matches_type(SyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: environment = client.environments.list( @@ -88,9 +76,7 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.environments.with_raw_response.list() @@ -100,9 +86,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: environment = response.parse() assert_matches_type(SyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.environments.with_streaming_response.list() as response: @@ -116,11 +100,11 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: class TestAsyncEnvironments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment = await async_client.environments.retrieve( @@ -128,9 +112,7 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(Environment, environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.environments.with_raw_response.retrieve( @@ -142,9 +124,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None environment = await response.parse() assert_matches_type(Environment, environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.environments.with_streaming_response.retrieve( @@ -158,9 +138,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `environment_slug` but received ''"): @@ -168,17 +146,13 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: "", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: environment = await async_client.environments.list() assert_matches_type(AsyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: environment = await async_client.environments.list( @@ -188,9 +162,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - ) assert_matches_type(AsyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.environments.with_raw_response.list() @@ -200,9 +172,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: environment = await response.parse() assert_matches_type(AsyncEntriesCursor[Environment], environment, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.environments.with_streaming_response.list() as response: diff --git a/tests/api_resources/test_guides.py b/tests/api_resources/test_guides.py new file mode 100644 index 00000000..e2f74475 --- /dev/null +++ b/tests/api_resources/test_guides.py @@ -0,0 +1,1124 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from knock_mapi import KnockMgmt, AsyncKnockMgmt +from tests.utils import assert_matches_type +from knock_mapi.types import ( + Guide, + GuideUpsertResponse, + GuideArchiveResponse, + GuideActivateResponse, + GuideValidateResponse, +) +from knock_mapi._utils import parse_datetime +from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestGuides: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + guide = client.guides.retrieve( + guide_key="guide_key", + environment="development", + ) + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: + guide = client.guides.retrieve( + guide_key="guide_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.retrieve( + guide_key="guide_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.retrieve( + guide_key="guide_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(Guide, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.retrieve( + guide_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: KnockMgmt) -> None: + guide = client.guides.list( + environment="development", + ) + assert_matches_type(SyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: KnockMgmt) -> None: + guide = client.guides.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(SyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(SyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(SyncEntriesCursor[Guide], guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate_overload_1(self, client: KnockMgmt) -> None: + guide = client.guides.activate( + guide_key="guide_key", + environment="development", + status=True, + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate_with_all_params_overload_1(self, client: KnockMgmt) -> None: + guide = client.guides.activate( + guide_key="guide_key", + environment="development", + status=True, + branch="feature-branch", + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_activate_overload_1(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.activate( + guide_key="guide_key", + environment="development", + status=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_activate_overload_1(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.activate( + guide_key="guide_key", + environment="development", + status=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_activate_overload_1(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.activate( + guide_key="", + environment="development", + status=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate_overload_2(self, client: KnockMgmt) -> None: + guide = client.guides.activate( + guide_key="guide_key", + environment="development", + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate_with_all_params_overload_2(self, client: KnockMgmt) -> None: + guide = client.guides.activate( + guide_key="guide_key", + environment="development", + branch="feature-branch", + from_=parse_datetime("2024-03-20T10:00:00Z"), + until=parse_datetime("2024-03-21T10:00:00Z"), + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_activate_overload_2(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.activate( + guide_key="guide_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_activate_overload_2(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.activate( + guide_key="guide_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_activate_overload_2(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.activate( + guide_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_archive(self, client: KnockMgmt) -> None: + guide = client.guides.archive( + "guide_key", + ) + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_archive(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.archive( + "guide_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_archive(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.archive( + "guide_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_archive(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.archive( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert(self, client: KnockMgmt) -> None: + guide = client.guides.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: + guide = client.guides.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + "name": "Welcome to the App", + "values": {"text_field": "bar"}, + } + ], + "activation_url_patterns": [ + { + "directive": "allow", + "pathname": "/dashboard/*", + "search": "tab=settings", + } + ], + "archived_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "deleted_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "description": "A guide to help users get started with the application", + "target_audience_id": None, + "target_property_conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + }, + annotate=True, + branch="feature-branch", + commit=True, + commit_message="commit_message", + ) + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_upsert(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_upsert(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_upsert(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.upsert( + guide_key="", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate(self, client: KnockMgmt) -> None: + guide = client.guides.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: + guide = client.guides.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + "name": "Welcome to the App", + "values": {"text_field": "bar"}, + } + ], + "activation_url_patterns": [ + { + "directive": "allow", + "pathname": "/dashboard/*", + "search": "tab=settings", + } + ], + "archived_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "deleted_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "description": "A guide to help users get started with the application", + "target_audience_id": None, + "target_property_conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + }, + branch="feature-branch", + ) + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_validate(self, client: KnockMgmt) -> None: + response = client.guides.with_raw_response.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = response.parse() + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_validate(self, client: KnockMgmt) -> None: + with client.guides.with_streaming_response.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = response.parse() + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_validate(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + client.guides.with_raw_response.validate( + guide_key="", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + +class TestAsyncGuides: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.retrieve( + guide_key="guide_key", + environment="development", + ) + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.retrieve( + guide_key="guide_key", + environment="development", + annotate=True, + branch="feature-branch", + hide_uncommitted_changes=True, + ) + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.retrieve( + guide_key="guide_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(Guide, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.retrieve( + guide_key="guide_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(Guide, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.retrieve( + guide_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.list( + environment="development", + ) + assert_matches_type(AsyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.list( + environment="development", + after="after", + annotate=True, + before="before", + branch="feature-branch", + hide_uncommitted_changes=True, + limit=0, + ) + assert_matches_type(AsyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.list( + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(AsyncEntriesCursor[Guide], guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.list( + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(AsyncEntriesCursor[Guide], guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate_overload_1(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.activate( + guide_key="guide_key", + environment="development", + status=True, + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate_with_all_params_overload_1(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.activate( + guide_key="guide_key", + environment="development", + status=True, + branch="feature-branch", + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_activate_overload_1(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.activate( + guide_key="guide_key", + environment="development", + status=True, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_activate_overload_1(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.activate( + guide_key="guide_key", + environment="development", + status=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_activate_overload_1(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.activate( + guide_key="", + environment="development", + status=True, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate_overload_2(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.activate( + guide_key="guide_key", + environment="development", + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate_with_all_params_overload_2(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.activate( + guide_key="guide_key", + environment="development", + branch="feature-branch", + from_=parse_datetime("2024-03-20T10:00:00Z"), + until=parse_datetime("2024-03-21T10:00:00Z"), + ) + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_activate_overload_2(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.activate( + guide_key="guide_key", + environment="development", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_activate_overload_2(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.activate( + guide_key="guide_key", + environment="development", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(GuideActivateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_activate_overload_2(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.activate( + guide_key="", + environment="development", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_archive(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.archive( + "guide_key", + ) + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_archive(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.archive( + "guide_key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_archive(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.archive( + "guide_key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(GuideArchiveResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_archive(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.archive( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + "name": "Welcome to the App", + "values": {"text_field": "bar"}, + } + ], + "activation_url_patterns": [ + { + "directive": "allow", + "pathname": "/dashboard/*", + "search": "tab=settings", + } + ], + "archived_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "deleted_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "description": "A guide to help users get started with the application", + "target_audience_id": None, + "target_property_conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + }, + annotate=True, + branch="feature-branch", + commit=True, + commit_message="commit_message", + ) + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.upsert( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(GuideUpsertResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.upsert( + guide_key="", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + guide = await async_client.guides.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + "name": "Welcome to the App", + "values": {"text_field": "bar"}, + } + ], + "activation_url_patterns": [ + { + "directive": "allow", + "pathname": "/dashboard/*", + "search": "tab=settings", + } + ], + "archived_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "deleted_at": parse_datetime("2019-12-27T18:11:19.117Z"), + "description": "A guide to help users get started with the application", + "target_audience_id": None, + "target_property_conditions": { + "all": [ + { + "operator": "equal_to", + "variable": "recipient.property", + "argument": "some_property", + } + ] + }, + }, + branch="feature-branch", + ) + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.guides.with_raw_response.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + guide = await response.parse() + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.guides.with_streaming_response.validate( + guide_key="guide_key", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + guide = await response.parse() + assert_matches_type(GuideValidateResponse, guide, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `guide_key` but received ''"): + await async_client.guides.with_raw_response.validate( + guide_key="", + environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [ + { + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + } + ], + }, + ) diff --git a/tests/api_resources/test_members.py b/tests/api_resources/test_members.py new file mode 100644 index 00000000..ab0362f6 --- /dev/null +++ b/tests/api_resources/test_members.py @@ -0,0 +1,273 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from knock_mapi import KnockMgmt, AsyncKnockMgmt +from tests.utils import assert_matches_type +from knock_mapi.types import Member +from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMembers: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + member = client.members.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Member, member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.members.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = response.parse() + assert_matches_type(Member, member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.members.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = response.parse() + assert_matches_type(Member, member, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.members.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: KnockMgmt) -> None: + member = client.members.list() + assert_matches_type(SyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: KnockMgmt) -> None: + member = client.members.list( + after="after", + before="before", + email="email", + limit=0, + role="role", + ) + assert_matches_type(SyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: KnockMgmt) -> None: + response = client.members.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = response.parse() + assert_matches_type(SyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: KnockMgmt) -> None: + with client.members.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = response.parse() + assert_matches_type(SyncEntriesCursor[Member], member, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: KnockMgmt) -> None: + member = client.members.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert member is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: KnockMgmt) -> None: + response = client.members.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = response.parse() + assert member is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: KnockMgmt) -> None: + with client.members.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = response.parse() + assert member is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.members.with_raw_response.delete( + "", + ) + + +class TestAsyncMembers: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + member = await async_client.members.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(Member, member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.members.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = await response.parse() + assert_matches_type(Member, member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.members.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = await response.parse() + assert_matches_type(Member, member, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.members.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: + member = await async_client.members.list() + assert_matches_type(AsyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + member = await async_client.members.list( + after="after", + before="before", + email="email", + limit=0, + role="role", + ) + assert_matches_type(AsyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.members.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = await response.parse() + assert_matches_type(AsyncEntriesCursor[Member], member, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.members.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = await response.parse() + assert_matches_type(AsyncEntriesCursor[Member], member, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncKnockMgmt) -> None: + member = await async_client.members.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert member is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.members.with_raw_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + member = await response.parse() + assert member is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.members.with_streaming_response.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + member = await response.parse() + assert member is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.members.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_message_types.py b/tests/api_resources/test_message_types.py index 70990d1b..5c138aa9 100644 --- a/tests/api_resources/test_message_types.py +++ b/tests/api_resources/test_message_types.py @@ -22,9 +22,7 @@ class TestMessageTypes: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: message_type = client.message_types.retrieve( @@ -33,22 +31,19 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: message_type = client.message_types.retrieve( message_type_key="email", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.message_types.with_raw_response.retrieve( @@ -61,9 +56,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: message_type = response.parse() assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.message_types.with_streaming_response.retrieve( @@ -78,9 +71,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): @@ -89,9 +80,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: message_type = client.message_types.list( @@ -99,9 +88,7 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: message_type = client.message_types.list( @@ -109,14 +96,13 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(SyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.message_types.with_raw_response.list( @@ -128,9 +114,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: message_type = response.parse() assert_matches_type(SyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.message_types.with_streaming_response.list( @@ -144,9 +128,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert(self, client: KnockMgmt) -> None: message_type = client.message_types.upsert( @@ -160,9 +142,7 @@ def test_method_upsert(self, client: KnockMgmt) -> None: ) assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: message_type = client.message_types.upsert( @@ -186,6 +166,7 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "description": "A description of the text field", "max_length": 100, "min_length": 10, + "placeholder": "A placeholder for the field.", "required": True, }, } @@ -196,14 +177,13 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: ], }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_upsert(self, client: KnockMgmt) -> None: response = client.message_types.with_raw_response.upsert( @@ -221,9 +201,7 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: message_type = response.parse() assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_upsert(self, client: KnockMgmt) -> None: with client.message_types.with_streaming_response.upsert( @@ -243,9 +221,7 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_upsert(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): @@ -259,9 +235,7 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate(self, client: KnockMgmt) -> None: message_type = client.message_types.validate( @@ -275,9 +249,7 @@ def test_method_validate(self, client: KnockMgmt) -> None: ) assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: message_type = client.message_types.validate( @@ -301,6 +273,7 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "description": "A description of the text field", "max_length": 100, "min_length": 10, + "placeholder": "A placeholder for the field.", "required": True, }, } @@ -310,12 +283,11 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: } ], }, + branch="feature-branch", ) assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_validate(self, client: KnockMgmt) -> None: response = client.message_types.with_raw_response.validate( @@ -333,9 +305,7 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: message_type = response.parse() assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_validate(self, client: KnockMgmt) -> None: with client.message_types.with_streaming_response.validate( @@ -355,9 +325,7 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_validate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): @@ -373,11 +341,11 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: class TestAsyncMessageTypes: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.retrieve( @@ -386,22 +354,19 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.retrieve( message_type_key="email", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.message_types.with_raw_response.retrieve( @@ -414,9 +379,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None message_type = await response.parse() assert_matches_type(MessageType, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.message_types.with_streaming_response.retrieve( @@ -431,9 +394,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): @@ -442,9 +403,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.list( @@ -452,9 +411,7 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.list( @@ -462,14 +419,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(AsyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.message_types.with_raw_response.list( @@ -481,9 +437,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: message_type = await response.parse() assert_matches_type(AsyncEntriesCursor[MessageType], message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.message_types.with_streaming_response.list( @@ -497,9 +451,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.upsert( @@ -513,9 +465,7 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.upsert( @@ -539,6 +489,7 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "description": "A description of the text field", "max_length": 100, "min_length": 10, + "placeholder": "A placeholder for the field.", "required": True, }, } @@ -549,14 +500,13 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) ], }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.message_types.with_raw_response.upsert( @@ -574,9 +524,7 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: message_type = await response.parse() assert_matches_type(MessageTypeUpsertResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: async with async_client.message_types.with_streaming_response.upsert( @@ -596,9 +544,7 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): @@ -612,9 +558,7 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.validate( @@ -628,9 +572,7 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: message_type = await async_client.message_types.validate( @@ -654,6 +596,7 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "description": "A description of the text field", "max_length": 100, "min_length": 10, + "placeholder": "A placeholder for the field.", "required": True, }, } @@ -663,12 +606,11 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm } ], }, + branch="feature-branch", ) assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.message_types.with_raw_response.validate( @@ -686,9 +628,7 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None message_type = await response.parse() assert_matches_type(MessageTypeValidateResponse, message_type, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.message_types.with_streaming_response.validate( @@ -708,9 +648,7 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_type_key` but received ''"): diff --git a/tests/api_resources/test_partials.py b/tests/api_resources/test_partials.py index c8c2c0ec..841c35d2 100644 --- a/tests/api_resources/test_partials.py +++ b/tests/api_resources/test_partials.py @@ -22,9 +22,7 @@ class TestPartials: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: partial = client.partials.retrieve( @@ -33,22 +31,19 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: partial = client.partials.retrieve( partial_key="partial_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.partials.with_raw_response.retrieve( @@ -61,9 +56,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: partial = response.parse() assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.partials.with_streaming_response.retrieve( @@ -78,9 +71,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): @@ -89,9 +80,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: partial = client.partials.list( @@ -99,9 +88,7 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: partial = client.partials.list( @@ -109,14 +96,13 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(SyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.partials.with_raw_response.list( @@ -128,9 +114,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: partial = response.parse() assert_matches_type(SyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.partials.with_streaming_response.list( @@ -144,9 +128,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert(self, client: KnockMgmt) -> None: partial = client.partials.upsert( @@ -160,9 +142,7 @@ def test_method_upsert(self, client: KnockMgmt) -> None: ) assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: partial = client.partials.upsert( @@ -172,19 +152,33 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "content": "

Hello, world!

", "name": "My Partial", "type": "html", - "description": "description", + "description": "This is a test partial", "icon_name": "icon_name", - "visual_block_enabled": False, + "input_schema": [ + { + "key": "text_field", + "label": "My text field", + "type": "text", + "settings": { + "default": "A placeholder", + "description": "A description of the text field", + "max_length": 100, + "min_length": 10, + "placeholder": "A placeholder for the field.", + "required": True, + }, + } + ], + "visual_block_enabled": True, }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_upsert(self, client: KnockMgmt) -> None: response = client.partials.with_raw_response.upsert( @@ -202,9 +196,7 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: partial = response.parse() assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_upsert(self, client: KnockMgmt) -> None: with client.partials.with_streaming_response.upsert( @@ -224,9 +216,7 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_upsert(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): @@ -240,9 +230,7 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate(self, client: KnockMgmt) -> None: partial = client.partials.validate( @@ -256,9 +244,7 @@ def test_method_validate(self, client: KnockMgmt) -> None: ) assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: partial = client.partials.validate( @@ -268,16 +254,30 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "content": "

Hello, world!

", "name": "My Partial", "type": "html", - "description": "description", + "description": "This is a test partial", "icon_name": "icon_name", - "visual_block_enabled": False, + "input_schema": [ + { + "key": "text_field", + "label": "My text field", + "type": "text", + "settings": { + "default": "A placeholder", + "description": "A description of the text field", + "max_length": 100, + "min_length": 10, + "placeholder": "A placeholder for the field.", + "required": True, + }, + } + ], + "visual_block_enabled": True, }, + branch="feature-branch", ) assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_validate(self, client: KnockMgmt) -> None: response = client.partials.with_raw_response.validate( @@ -295,9 +295,7 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: partial = response.parse() assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_validate(self, client: KnockMgmt) -> None: with client.partials.with_streaming_response.validate( @@ -317,9 +315,7 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_validate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): @@ -335,11 +331,11 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: class TestAsyncPartials: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.retrieve( @@ -348,22 +344,19 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.retrieve( partial_key="partial_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.partials.with_raw_response.retrieve( @@ -376,9 +369,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None partial = await response.parse() assert_matches_type(Partial, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.partials.with_streaming_response.retrieve( @@ -393,9 +384,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): @@ -404,9 +393,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.list( @@ -414,9 +401,7 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.list( @@ -424,14 +409,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(AsyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.partials.with_raw_response.list( @@ -443,9 +427,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: partial = await response.parse() assert_matches_type(AsyncEntriesCursor[Partial], partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.partials.with_streaming_response.list( @@ -459,9 +441,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.upsert( @@ -475,9 +455,7 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.upsert( @@ -487,19 +465,33 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "content": "

Hello, world!

", "name": "My Partial", "type": "html", - "description": "description", + "description": "This is a test partial", "icon_name": "icon_name", - "visual_block_enabled": False, + "input_schema": [ + { + "key": "text_field", + "label": "My text field", + "type": "text", + "settings": { + "default": "A placeholder", + "description": "A description of the text field", + "max_length": 100, + "min_length": 10, + "placeholder": "A placeholder for the field.", + "required": True, + }, + } + ], + "visual_block_enabled": True, }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.partials.with_raw_response.upsert( @@ -517,9 +509,7 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: partial = await response.parse() assert_matches_type(PartialUpsertResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: async with async_client.partials.with_streaming_response.upsert( @@ -539,9 +529,7 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): @@ -555,9 +543,7 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.validate( @@ -571,9 +557,7 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: partial = await async_client.partials.validate( @@ -583,16 +567,30 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "content": "

Hello, world!

", "name": "My Partial", "type": "html", - "description": "description", + "description": "This is a test partial", "icon_name": "icon_name", - "visual_block_enabled": False, + "input_schema": [ + { + "key": "text_field", + "label": "My text field", + "type": "text", + "settings": { + "default": "A placeholder", + "description": "A description of the text field", + "max_length": 100, + "min_length": 10, + "placeholder": "A placeholder for the field.", + "required": True, + }, + } + ], + "visual_block_enabled": True, }, + branch="feature-branch", ) assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.partials.with_raw_response.validate( @@ -610,9 +608,7 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None partial = await response.parse() assert_matches_type(PartialValidateResponse, partial, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.partials.with_streaming_response.validate( @@ -632,9 +628,7 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `partial_key` but received ''"): diff --git a/tests/api_resources/test_translations.py b/tests/api_resources/test_translations.py index 33204817..3482a36e 100644 --- a/tests/api_resources/test_translations.py +++ b/tests/api_resources/test_translations.py @@ -23,9 +23,7 @@ class TestTranslations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: translation = client.translations.retrieve( @@ -34,24 +32,21 @@ def test_method_retrieve(self, client: KnockMgmt) -> None: ) assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: translation = client.translations.retrieve( locale_code="locale_code", environment="development", annotate=True, + branch="feature-branch", format="json", hide_uncommitted_changes=True, namespace="namespace", ) assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.translations.with_raw_response.retrieve( @@ -64,9 +59,7 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: translation = response.parse() assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.translations.with_streaming_response.retrieve( @@ -81,9 +74,7 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): @@ -92,9 +83,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: translation = client.translations.list( @@ -102,9 +91,7 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: translation = client.translations.list( @@ -112,6 +99,7 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: after="after", annotate=True, before="before", + branch="feature-branch", format="json", hide_uncommitted_changes=True, limit=0, @@ -120,9 +108,7 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.translations.with_raw_response.list( @@ -134,9 +120,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: translation = response.parse() assert_matches_type(SyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.translations.with_streaming_response.list( @@ -150,9 +134,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert(self, client: KnockMgmt) -> None: translation = client.translations.upsert( @@ -166,9 +148,7 @@ def test_method_upsert(self, client: KnockMgmt) -> None: ) assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: translation = client.translations.upsert( @@ -180,15 +160,14 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "format": "json", }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", format="json", ) assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_upsert(self, client: KnockMgmt) -> None: response = client.translations.with_raw_response.upsert( @@ -206,9 +185,7 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: translation = response.parse() assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_upsert(self, client: KnockMgmt) -> None: with client.translations.with_streaming_response.upsert( @@ -228,9 +205,7 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_upsert(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): @@ -244,9 +219,7 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate(self, client: KnockMgmt) -> None: translation = client.translations.validate( @@ -259,9 +232,21 @@ def test_method_validate(self, client: KnockMgmt) -> None: ) assert_matches_type(TranslationValidateResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: + translation = client.translations.validate( + locale_code="locale_code", + environment="development", + translation={ + "content": '{"hello":"Hello, world!"}', + "format": "json", + }, + branch="feature-branch", + ) + assert_matches_type(TranslationValidateResponse, translation, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_validate(self, client: KnockMgmt) -> None: response = client.translations.with_raw_response.validate( @@ -278,9 +263,7 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: translation = response.parse() assert_matches_type(TranslationValidateResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_validate(self, client: KnockMgmt) -> None: with client.translations.with_streaming_response.validate( @@ -299,9 +282,7 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_validate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): @@ -316,11 +297,11 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: class TestAsyncTranslations: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.retrieve( @@ -329,24 +310,21 @@ async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.retrieve( locale_code="locale_code", environment="development", annotate=True, + branch="feature-branch", format="json", hide_uncommitted_changes=True, namespace="namespace", ) assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.translations.with_raw_response.retrieve( @@ -359,9 +337,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None translation = await response.parse() assert_matches_type(TranslationRetrieveResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.translations.with_streaming_response.retrieve( @@ -376,9 +352,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): @@ -387,9 +361,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.list( @@ -397,9 +369,7 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.list( @@ -407,6 +377,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - after="after", annotate=True, before="before", + branch="feature-branch", format="json", hide_uncommitted_changes=True, limit=0, @@ -415,9 +386,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - ) assert_matches_type(AsyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.translations.with_raw_response.list( @@ -429,9 +398,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: translation = await response.parse() assert_matches_type(AsyncEntriesCursor[Translation], translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.translations.with_streaming_response.list( @@ -445,9 +412,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.upsert( @@ -461,9 +426,7 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.upsert( @@ -475,15 +438,14 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "format": "json", }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", format="json", ) assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.translations.with_raw_response.upsert( @@ -501,9 +463,7 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: translation = await response.parse() assert_matches_type(TranslationUpsertResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: async with async_client.translations.with_streaming_response.upsert( @@ -523,9 +483,7 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): @@ -539,9 +497,7 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: translation = await async_client.translations.validate( @@ -554,9 +510,21 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(TranslationValidateResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + translation = await async_client.translations.validate( + locale_code="locale_code", + environment="development", + translation={ + "content": '{"hello":"Hello, world!"}', + "format": "json", + }, + branch="feature-branch", + ) + assert_matches_type(TranslationValidateResponse, translation, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.translations.with_raw_response.validate( @@ -573,9 +541,7 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None translation = await response.parse() assert_matches_type(TranslationValidateResponse, translation, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.translations.with_streaming_response.validate( @@ -594,9 +560,7 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `locale_code` but received ''"): diff --git a/tests/api_resources/test_variables.py b/tests/api_resources/test_variables.py index c3eeb56b..b3e3431f 100644 --- a/tests/api_resources/test_variables.py +++ b/tests/api_resources/test_variables.py @@ -18,9 +18,49 @@ class TestVariables: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: KnockMgmt) -> None: + variable = client.variables.retrieve( + "key", + ) + assert_matches_type(Variable, variable, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: KnockMgmt) -> None: + response = client.variables.with_raw_response.retrieve( + "key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + variable = response.parse() + assert_matches_type(Variable, variable, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: + with client.variables.with_streaming_response.retrieve( + "key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + variable = response.parse() + assert_matches_type(Variable, variable, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: KnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"): + client.variables.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: variable = client.variables.list( @@ -28,22 +68,20 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: variable = client.variables.list( environment="development", after="after", before="before", + branch="feature-branch", limit=0, + type="public", ) assert_matches_type(SyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.variables.with_raw_response.list( @@ -55,9 +93,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: variable = response.parse() assert_matches_type(SyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.variables.with_streaming_response.list( @@ -73,11 +109,53 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: class TestAsyncVariables: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: + variable = await async_client.variables.retrieve( + "key", + ) + assert_matches_type(Variable, variable, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + response = await async_client.variables.with_raw_response.retrieve( + "key", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + variable = await response.parse() + assert_matches_type(Variable, variable, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: + async with async_client.variables.with_streaming_response.retrieve( + "key", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + variable = await response.parse() + assert_matches_type(Variable, variable, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"): + await async_client.variables.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: variable = await async_client.variables.list( @@ -85,22 +163,20 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: variable = await async_client.variables.list( environment="development", after="after", before="before", + branch="feature-branch", limit=0, + type="public", ) assert_matches_type(AsyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.variables.with_raw_response.list( @@ -112,9 +188,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: variable = await response.parse() assert_matches_type(AsyncEntriesCursor[Variable], variable, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.variables.with_streaming_response.list( diff --git a/tests/api_resources/test_workflows.py b/tests/api_resources/test_workflows.py index 497a2243..74467ea4 100644 --- a/tests/api_resources/test_workflows.py +++ b/tests/api_resources/test_workflows.py @@ -14,6 +14,7 @@ WorkflowRunResponse, WorkflowUpsertResponse, WorkflowActivateResponse, + WorkflowRetrieveResponse, WorkflowValidateResponse, ) from knock_mapi.pagination import SyncEntriesCursor, AsyncEntriesCursor @@ -24,33 +25,28 @@ class TestWorkflows: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: KnockMgmt) -> None: workflow = client.workflows.retrieve( workflow_key="workflow_key", environment="development", ) - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_with_all_params(self, client: KnockMgmt) -> None: workflow = client.workflows.retrieve( workflow_key="workflow_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.retrieve( @@ -61,11 +57,9 @@ def test_raw_response_retrieve(self, client: KnockMgmt) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" workflow = response.parse() - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.retrieve( @@ -76,13 +70,11 @@ def test_streaming_response_retrieve(self, client: KnockMgmt) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" workflow = response.parse() - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -91,9 +83,7 @@ def test_path_params_retrieve(self, client: KnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: KnockMgmt) -> None: workflow = client.workflows.list( @@ -101,9 +91,7 @@ def test_method_list(self, client: KnockMgmt) -> None: ) assert_matches_type(SyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: KnockMgmt) -> None: workflow = client.workflows.list( @@ -111,14 +99,13 @@ def test_method_list_with_all_params(self, client: KnockMgmt) -> None: after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(SyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.list( @@ -130,9 +117,7 @@ def test_raw_response_list(self, client: KnockMgmt) -> None: workflow = response.parse() assert_matches_type(SyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.list( @@ -146,9 +131,7 @@ def test_streaming_response_list(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_activate(self, client: KnockMgmt) -> None: workflow = client.workflows.activate( @@ -158,9 +141,18 @@ def test_method_activate(self, client: KnockMgmt) -> None: ) assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate_with_all_params(self, client: KnockMgmt) -> None: + workflow = client.workflows.activate( + workflow_key="workflow_key", + environment="development", + status=True, + branch="feature-branch", + ) + assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_activate(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.activate( @@ -174,9 +166,7 @@ def test_raw_response_activate(self, client: KnockMgmt) -> None: workflow = response.parse() assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_activate(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.activate( @@ -192,9 +182,7 @@ def test_streaming_response_activate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_activate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -204,9 +192,7 @@ def test_path_params_activate(self, client: KnockMgmt) -> None: status=True, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_run(self, client: KnockMgmt) -> None: workflow = client.workflows.run( @@ -216,15 +202,14 @@ def test_method_run(self, client: KnockMgmt) -> None: ) assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_run_with_all_params(self, client: KnockMgmt) -> None: workflow = client.workflows.run( workflow_key="workflow_key", environment="development", recipients=["dnedry"], + branch="feature-branch", actor={ "id": "project_1", "collection": "projects", @@ -235,9 +220,7 @@ def test_method_run_with_all_params(self, client: KnockMgmt) -> None: ) assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_run(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.run( @@ -251,9 +234,7 @@ def test_raw_response_run(self, client: KnockMgmt) -> None: workflow = response.parse() assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_run(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.run( @@ -269,9 +250,7 @@ def test_streaming_response_run(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_run(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -281,9 +260,7 @@ def test_path_params_run(self, client: KnockMgmt) -> None: recipients=["dnedry"], ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert(self, client: KnockMgmt) -> None: workflow = client.workflows.upsert( @@ -293,7 +270,6 @@ def test_method_upsert(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -303,9 +279,7 @@ def test_method_upsert(self, client: KnockMgmt) -> None: ) assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: workflow = client.workflows.upsert( @@ -315,7 +289,6 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": { "markdown_body": "Hello **{{ recipient.name }}**", @@ -328,19 +301,10 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "action_url": "{{ vars.app_url }}", }, "type": "channel", - "channel_group_key": "email", + "channel_group_key": None, "channel_key": "in-app-feed", - "channel_overrides": { - "bcc_address": None, - "cc_address": None, - "from_address": "hello@example.com", - "from_name": "John Doe", - "json_overrides": '{"some_override": true}', - "link_tracking": True, - "open_tracking": True, - "reply_to_address": None, - "to_address": "hello@example.com", - }, + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", "conditions": { "all": [ { @@ -350,13 +314,14 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: } ] }, - "description": "Delay for 10 seconds", + "description": "This is a description of the channel step", + "name": "Channel 1", "send_windows": [ { "day": "monday", "type": "send", - "from": "from", - "until": "until", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", } ], } @@ -380,14 +345,13 @@ def test_method_upsert_with_all_params(self, client: KnockMgmt) -> None: "trigger_frequency": "every_trigger", }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_upsert(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.upsert( @@ -397,7 +361,6 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -411,9 +374,7 @@ def test_raw_response_upsert(self, client: KnockMgmt) -> None: workflow = response.parse() assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_upsert(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.upsert( @@ -423,7 +384,6 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -439,9 +399,7 @@ def test_streaming_response_upsert(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_upsert(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -452,7 +410,6 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -461,9 +418,7 @@ def test_path_params_upsert(self, client: KnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate(self, client: KnockMgmt) -> None: workflow = client.workflows.validate( @@ -473,7 +428,6 @@ def test_method_validate(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -483,9 +437,7 @@ def test_method_validate(self, client: KnockMgmt) -> None: ) assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: workflow = client.workflows.validate( @@ -495,7 +447,6 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": { "markdown_body": "Hello **{{ recipient.name }}**", @@ -508,19 +459,10 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "action_url": "{{ vars.app_url }}", }, "type": "channel", - "channel_group_key": "email", + "channel_group_key": None, "channel_key": "in-app-feed", - "channel_overrides": { - "bcc_address": None, - "cc_address": None, - "from_address": "hello@example.com", - "from_name": "John Doe", - "json_overrides": '{"some_override": true}', - "link_tracking": True, - "open_tracking": True, - "reply_to_address": None, - "to_address": "hello@example.com", - }, + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", "conditions": { "all": [ { @@ -530,13 +472,14 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: } ] }, - "description": "Delay for 10 seconds", + "description": "This is a description of the channel step", + "name": "Channel 1", "send_windows": [ { "day": "monday", "type": "send", - "from": "from", - "until": "until", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", } ], } @@ -559,12 +502,11 @@ def test_method_validate_with_all_params(self, client: KnockMgmt) -> None: "trigger_data_json_schema": {"foo": "bar"}, "trigger_frequency": "every_trigger", }, + branch="feature-branch", ) assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_validate(self, client: KnockMgmt) -> None: response = client.workflows.with_raw_response.validate( @@ -574,7 +516,6 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -588,9 +529,7 @@ def test_raw_response_validate(self, client: KnockMgmt) -> None: workflow = response.parse() assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_validate(self, client: KnockMgmt) -> None: with client.workflows.with_streaming_response.validate( @@ -600,7 +539,6 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -616,9 +554,7 @@ def test_streaming_response_validate(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_validate(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -629,7 +565,6 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -640,35 +575,32 @@ def test_path_params_validate(self, client: KnockMgmt) -> None: class TestAsyncWorkflows: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.retrieve( workflow_key="workflow_key", environment="development", ) - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.retrieve( workflow_key="workflow_key", environment="development", annotate=True, + branch="feature-branch", hide_uncommitted_changes=True, ) - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.retrieve( @@ -679,11 +611,9 @@ async def test_raw_response_retrieve(self, async_client: AsyncKnockMgmt) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" workflow = await response.parse() - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.retrieve( @@ -694,13 +624,11 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKnockMgmt) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" workflow = await response.parse() - assert_matches_type(Workflow, workflow, path=["response"]) + assert_matches_type(WorkflowRetrieveResponse, workflow, path=["response"]) assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -709,9 +637,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKnockMgmt) -> None: environment="development", ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.list( @@ -719,9 +645,7 @@ async def test_method_list(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(AsyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.list( @@ -729,14 +653,13 @@ async def test_method_list_with_all_params(self, async_client: AsyncKnockMgmt) - after="after", annotate=True, before="before", + branch="feature-branch", hide_uncommitted_changes=True, limit=0, ) assert_matches_type(AsyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.list( @@ -748,9 +671,7 @@ async def test_raw_response_list(self, async_client: AsyncKnockMgmt) -> None: workflow = await response.parse() assert_matches_type(AsyncEntriesCursor[Workflow], workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.list( @@ -764,9 +685,7 @@ async def test_streaming_response_list(self, async_client: AsyncKnockMgmt) -> No assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_activate(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.activate( @@ -776,9 +695,18 @@ async def test_method_activate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: + workflow = await async_client.workflows.activate( + workflow_key="workflow_key", + environment="development", + status=True, + branch="feature-branch", + ) + assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_activate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.activate( @@ -792,9 +720,7 @@ async def test_raw_response_activate(self, async_client: AsyncKnockMgmt) -> None workflow = await response.parse() assert_matches_type(WorkflowActivateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_activate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.activate( @@ -810,9 +736,7 @@ async def test_streaming_response_activate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_activate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -822,9 +746,7 @@ async def test_path_params_activate(self, async_client: AsyncKnockMgmt) -> None: status=True, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_run(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.run( @@ -834,15 +756,14 @@ async def test_method_run(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_run_with_all_params(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.run( workflow_key="workflow_key", environment="development", recipients=["dnedry"], + branch="feature-branch", actor={ "id": "project_1", "collection": "projects", @@ -853,9 +774,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncKnockMgmt) -> ) assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_run(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.run( @@ -869,9 +788,7 @@ async def test_raw_response_run(self, async_client: AsyncKnockMgmt) -> None: workflow = await response.parse() assert_matches_type(WorkflowRunResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_run(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.run( @@ -887,9 +804,7 @@ async def test_streaming_response_run(self, async_client: AsyncKnockMgmt) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_run(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -899,9 +814,7 @@ async def test_path_params_run(self, async_client: AsyncKnockMgmt) -> None: recipients=["dnedry"], ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.upsert( @@ -911,7 +824,6 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -921,9 +833,7 @@ async def test_method_upsert(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.upsert( @@ -933,7 +843,6 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": { "markdown_body": "Hello **{{ recipient.name }}**", @@ -946,19 +855,10 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "action_url": "{{ vars.app_url }}", }, "type": "channel", - "channel_group_key": "email", + "channel_group_key": None, "channel_key": "in-app-feed", - "channel_overrides": { - "bcc_address": None, - "cc_address": None, - "from_address": "hello@example.com", - "from_name": "John Doe", - "json_overrides": '{"some_override": true}', - "link_tracking": True, - "open_tracking": True, - "reply_to_address": None, - "to_address": "hello@example.com", - }, + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", "conditions": { "all": [ { @@ -968,13 +868,14 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) } ] }, - "description": "Delay for 10 seconds", + "description": "This is a description of the channel step", + "name": "Channel 1", "send_windows": [ { "day": "monday", "type": "send", - "from": "from", - "until": "until", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", } ], } @@ -998,14 +899,13 @@ async def test_method_upsert_with_all_params(self, async_client: AsyncKnockMgmt) "trigger_frequency": "every_trigger", }, annotate=True, + branch="feature-branch", commit=True, commit_message="commit_message", ) assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.upsert( @@ -1015,7 +915,6 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1029,9 +928,7 @@ async def test_raw_response_upsert(self, async_client: AsyncKnockMgmt) -> None: workflow = await response.parse() assert_matches_type(WorkflowUpsertResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.upsert( @@ -1041,7 +938,6 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1057,9 +953,7 @@ async def test_streaming_response_upsert(self, async_client: AsyncKnockMgmt) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -1070,7 +964,6 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1079,9 +972,7 @@ async def test_path_params_upsert(self, async_client: AsyncKnockMgmt) -> None: }, ) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.validate( @@ -1091,7 +982,6 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1101,9 +991,7 @@ async def test_method_validate(self, async_client: AsyncKnockMgmt) -> None: ) assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgmt) -> None: workflow = await async_client.workflows.validate( @@ -1113,7 +1001,6 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": { "markdown_body": "Hello **{{ recipient.name }}**", @@ -1126,19 +1013,10 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "action_url": "{{ vars.app_url }}", }, "type": "channel", - "channel_group_key": "email", + "channel_group_key": None, "channel_key": "in-app-feed", - "channel_overrides": { - "bcc_address": None, - "cc_address": None, - "from_address": "hello@example.com", - "from_name": "John Doe", - "json_overrides": '{"some_override": true}', - "link_tracking": True, - "open_tracking": True, - "reply_to_address": None, - "to_address": "hello@example.com", - }, + "channel_overrides": {"link_tracking": True}, + "channel_type": "in_app_feed", "conditions": { "all": [ { @@ -1148,13 +1026,14 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm } ] }, - "description": "Delay for 10 seconds", + "description": "This is a description of the channel step", + "name": "Channel 1", "send_windows": [ { "day": "monday", "type": "send", - "from": "from", - "until": "until", + "from": "18:11:19.117Z", + "until": "18:11:19.117Z", } ], } @@ -1177,12 +1056,11 @@ async def test_method_validate_with_all_params(self, async_client: AsyncKnockMgm "trigger_data_json_schema": {"foo": "bar"}, "trigger_frequency": "every_trigger", }, + branch="feature-branch", ) assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.with_raw_response.validate( @@ -1192,7 +1070,6 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1206,9 +1083,7 @@ async def test_raw_response_validate(self, async_client: AsyncKnockMgmt) -> None workflow = await response.parse() assert_matches_type(WorkflowValidateResponse, workflow, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.with_streaming_response.validate( @@ -1218,7 +1093,6 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", @@ -1234,9 +1108,7 @@ async def test_streaming_response_validate(self, async_client: AsyncKnockMgmt) - assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -1247,7 +1119,6 @@ async def test_path_params_validate(self, async_client: AsyncKnockMgmt) -> None: "name": "My Workflow", "steps": [ { - "name": "Channel 1", "ref": "channel_1", "template": {"markdown_body": "Hello **{{ recipient.name }}**"}, "type": "channel", diff --git a/tests/api_resources/workflows/test_steps.py b/tests/api_resources/workflows/test_steps.py index aede7310..37be6061 100644 --- a/tests/api_resources/workflows/test_steps.py +++ b/tests/api_resources/workflows/test_steps.py @@ -17,9 +17,7 @@ class TestSteps: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_preview_template(self, client: KnockMgmt) -> None: step = client.workflows.steps.preview_template( @@ -30,9 +28,7 @@ def test_method_preview_template(self, client: KnockMgmt) -> None: ) assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_preview_template_with_all_params(self, client: KnockMgmt) -> None: step = client.workflows.steps.preview_template( @@ -40,15 +36,14 @@ def test_method_preview_template_with_all_params(self, client: KnockMgmt) -> Non workflow_key="workflow_key", environment="development", recipient="dnedry", + branch="feature-branch", actor="dnedry", data={"park_id": "bar"}, tenant="acme-corp", ) assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_preview_template(self, client: KnockMgmt) -> None: response = client.workflows.steps.with_raw_response.preview_template( @@ -63,9 +58,7 @@ def test_raw_response_preview_template(self, client: KnockMgmt) -> None: step = response.parse() assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_preview_template(self, client: KnockMgmt) -> None: with client.workflows.steps.with_streaming_response.preview_template( @@ -82,9 +75,7 @@ def test_streaming_response_preview_template(self, client: KnockMgmt) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_preview_template(self, client: KnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): @@ -105,11 +96,11 @@ def test_path_params_preview_template(self, client: KnockMgmt) -> None: class TestAsyncSteps: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_preview_template(self, async_client: AsyncKnockMgmt) -> None: step = await async_client.workflows.steps.preview_template( @@ -120,9 +111,7 @@ async def test_method_preview_template(self, async_client: AsyncKnockMgmt) -> No ) assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_preview_template_with_all_params(self, async_client: AsyncKnockMgmt) -> None: step = await async_client.workflows.steps.preview_template( @@ -130,15 +119,14 @@ async def test_method_preview_template_with_all_params(self, async_client: Async workflow_key="workflow_key", environment="development", recipient="dnedry", + branch="feature-branch", actor="dnedry", data={"park_id": "bar"}, tenant="acme-corp", ) assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_preview_template(self, async_client: AsyncKnockMgmt) -> None: response = await async_client.workflows.steps.with_raw_response.preview_template( @@ -153,9 +141,7 @@ async def test_raw_response_preview_template(self, async_client: AsyncKnockMgmt) step = await response.parse() assert_matches_type(StepPreviewTemplateResponse, step, path=["response"]) - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_preview_template(self, async_client: AsyncKnockMgmt) -> None: async with async_client.workflows.steps.with_streaming_response.preview_template( @@ -172,9 +158,7 @@ async def test_streaming_response_preview_template(self, async_client: AsyncKnoc assert cast(Any, response.is_closed) is True - @pytest.mark.skip( - reason="currently no good way to test endpoints defining callbacks, Prism mock server will fail trying to reach the provided callback url" - ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_preview_template(self, async_client: AsyncKnockMgmt) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `workflow_key` but received ''"): diff --git a/tests/conftest.py b/tests/conftest.py index c8f563bd..6df95602 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from knock_mapi import KnockMgmt, AsyncKnockMgmt +from knock_mapi import KnockMgmt, AsyncKnockMgmt, DefaultAioHttpClient +from knock_mapi._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -43,11 +60,25 @@ def client(request: FixtureRequest) -> Iterator[KnockMgmt]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncKnockMgmt]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") async with AsyncKnockMgmt( - base_url=base_url, service_token=service_token, _strict_response_validation=strict + base_url=base_url, service_token=service_token, _strict_response_validation=strict, http_client=http_client ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 2e1a8fda..847b27ba 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,15 +6,13 @@ import os import sys import json -import time import asyncio import inspect -import subprocess +import dataclasses import tracemalloc -from typing import Any, Union, cast -from textwrap import dedent +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -23,17 +21,23 @@ from knock_mapi import KnockMgmt, AsyncKnockMgmt, APIResponseValidationError from knock_mapi._types import Omit +from knock_mapi._utils import asyncify from knock_mapi._models import BaseModel, FinalRequestOptions -from knock_mapi._exceptions import KnockMgmtError, APIResponseValidationError +from knock_mapi._exceptions import APIStatusError, KnockMgmtError, APIResponseValidationError from knock_mapi._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + get_platform, make_request_options, ) from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") service_token = "My Service Token" @@ -48,52 +52,101 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 -class TestKnockMgmt: - client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +class TestKnockMgmt: @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: KnockMgmt) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + def test_raw_response_for_binary(self, respx_mock: MockRouter, client: KnockMgmt) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, client: KnockMgmt) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(service_token="another My Service Token") + copied = client.copy(service_token="another My Service Token") assert copied.service_token == "another My Service Token" - assert self.client.service_token == "My Service Token" + assert client.service_token == "My Service Token" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: KnockMgmt) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(client.timeout, httpx.Timeout) + copied = client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: client = KnockMgmt( @@ -131,6 +184,7 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + client.close() def test_copy_default_query(self) -> None: client = KnockMgmt( @@ -171,13 +225,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: KnockMgmt) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -187,12 +243,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, client: KnockMgmt) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -249,14 +306,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + def test_request_timeout(self, client: KnockMgmt) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) - ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(100.0) @@ -269,6 +324,8 @@ def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + client.close() + def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: @@ -283,6 +340,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + client.close() + # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = KnockMgmt( @@ -296,6 +355,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + client.close() + # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = KnockMgmt( @@ -309,6 +370,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + client.close() + async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: @@ -320,17 +383,17 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - client = KnockMgmt( + test_client = KnockMgmt( base_url=base_url, service_token=service_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = KnockMgmt( + test_client2 = KnockMgmt( base_url=base_url, service_token=service_token, _strict_response_validation=True, @@ -339,10 +402,13 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + test_client.close() + test_client2.close() + def test_validate_headers(self) -> None: client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -374,8 +440,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + client.close() + + def test_request_extra_json(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -386,7 +454,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -397,7 +465,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -408,8 +476,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -419,7 +487,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -430,8 +498,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -444,7 +512,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -458,7 +526,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -474,7 +542,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: KnockMgmt) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -501,7 +569,71 @@ def test_multipart_repeating_array(self, client: KnockMgmt) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_binary_content_upload(self, respx_mock: MockRouter, client: KnockMgmt) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with KnockMgmt( + base_url=base_url, + service_token=service_token, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: KnockMgmt) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + @pytest.mark.respx(base_url=base_url) + def test_basic_union_response(self, respx_mock: MockRouter, client: KnockMgmt) -> None: class Model1(BaseModel): name: str @@ -510,12 +642,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + def test_union_response_different_types(self, respx_mock: MockRouter, client: KnockMgmt) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -526,18 +658,18 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: KnockMgmt) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -553,7 +685,7 @@ class Model(BaseModel): ) ) - response = self.client.get("/foo", cast_to=Model) + response = client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 @@ -567,6 +699,8 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" + client.close() + def test_base_url_env(self) -> None: with update_env(KNOCK_MGMT_BASE_URL="http://localhost:5000/from/env"): client = KnockMgmt(service_token=service_token, _strict_response_validation=True) @@ -598,6 +732,7 @@ def test_base_url_trailing_slash(self, client: KnockMgmt) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -625,6 +760,7 @@ def test_base_url_no_trailing_slash(self, client: KnockMgmt) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -652,35 +788,36 @@ def test_absolute_request_url(self, client: KnockMgmt) -> None: ), ) assert request.url == "https://myapi.com/foo" + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - assert not client.is_closed() + test_client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: KnockMgmt) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - self.client.get("/foo", cast_to=Model) + client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) @@ -705,11 +842,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=False) + non_strict_client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=False) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -732,9 +872,9 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = KnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: KnockMgmt + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) @@ -821,57 +961,108 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter, client: KnockMgmt) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: KnockMgmt) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" -class TestAsyncKnockMgmt: - client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) +class TestAsyncKnockMgmt: @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, async_client: AsyncKnockMgmt) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(service_token="another My Service Token") + copied = async_client.copy(service_token="another My Service Token") assert copied.service_token == "another My Service Token" - assert self.client.service_token == "My Service Token" + assert async_client.service_token == "My Service Token" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncKnockMgmt) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert async_client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(async_client.timeout, httpx.Timeout) + copied = async_client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx.Timeout) - def test_copy_default_headers(self) -> None: + async def test_copy_default_headers(self) -> None: client = AsyncKnockMgmt( base_url=base_url, service_token=service_token, @@ -907,8 +1098,9 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + await client.close() - def test_copy_default_query(self) -> None: + async def test_copy_default_query(self) -> None: client = AsyncKnockMgmt( base_url=base_url, service_token=service_token, @@ -947,13 +1139,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + await client.close() + + def test_copy_signature(self, async_client: AsyncKnockMgmt) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + async_client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(async_client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -963,12 +1157,13 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" - def test_copy_build_request(self) -> None: + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") + def test_copy_build_request(self, async_client: AsyncKnockMgmt) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1025,12 +1220,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + async def test_request_timeout(self, async_client: AsyncKnockMgmt) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( + request = async_client._build_request( FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) ) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1045,6 +1240,8 @@ async def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + await client.close() + async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: @@ -1059,6 +1256,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + await client.close() + # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncKnockMgmt( @@ -1072,6 +1271,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + await client.close() + # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncKnockMgmt( @@ -1085,6 +1286,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + await client.close() + def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: @@ -1095,18 +1298,18 @@ def test_invalid_http_client(self) -> None: http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncKnockMgmt( + async def test_default_headers_option(self) -> None: + test_client = AsyncKnockMgmt( base_url=base_url, service_token=service_token, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncKnockMgmt( + test_client2 = AsyncKnockMgmt( base_url=base_url, service_token=service_token, _strict_response_validation=True, @@ -1115,10 +1318,13 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + await test_client.close() + await test_client2.close() + def test_validate_headers(self) -> None: client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1129,7 +1335,7 @@ def test_validate_headers(self) -> None: client2 = AsyncKnockMgmt(base_url=base_url, service_token=None, _strict_response_validation=True) _ = client2 - def test_default_query_option(self) -> None: + async def test_default_query_option(self) -> None: client = AsyncKnockMgmt( base_url=base_url, service_token=service_token, @@ -1150,8 +1356,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + await client.close() + + def test_request_extra_json(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1162,7 +1370,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1173,7 +1381,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1184,8 +1392,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1195,7 +1403,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1206,8 +1414,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: KnockMgmt) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1220,7 +1428,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1234,7 +1442,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1250,7 +1458,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncKnockMgmt) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1277,7 +1485,73 @@ def test_multipart_repeating_array(self, async_client: AsyncKnockMgmt) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncKnockMgmt( + base_url=base_url, + service_token=service_token, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncKnockMgmt + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + @pytest.mark.respx(base_url=base_url) + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: class Model1(BaseModel): name: str @@ -1286,12 +1560,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1302,18 +1576,20 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + async def test_non_application_json_content_type_for_json_data( + self, respx_mock: MockRouter, async_client: AsyncKnockMgmt + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1329,11 +1605,11 @@ class Model(BaseModel): ) ) - response = await self.client.get("/foo", cast_to=Model) + response = await async_client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 - def test_base_url_setter(self) -> None: + async def test_base_url_setter(self) -> None: client = AsyncKnockMgmt( base_url="https://example.com/from_init", service_token=service_token, _strict_response_validation=True ) @@ -1343,7 +1619,9 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" - def test_base_url_env(self) -> None: + await client.close() + + async def test_base_url_env(self) -> None: with update_env(KNOCK_MGMT_BASE_URL="http://localhost:5000/from/env"): client = AsyncKnockMgmt(service_token=service_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1365,7 +1643,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncKnockMgmt) -> None: + async def test_base_url_trailing_slash(self, client: AsyncKnockMgmt) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1374,6 +1652,7 @@ def test_base_url_trailing_slash(self, client: AsyncKnockMgmt) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1392,7 +1671,7 @@ def test_base_url_trailing_slash(self, client: AsyncKnockMgmt) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncKnockMgmt) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncKnockMgmt) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1401,6 +1680,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncKnockMgmt) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1419,7 +1699,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncKnockMgmt) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncKnockMgmt) -> None: + async def test_absolute_request_url(self, client: AsyncKnockMgmt) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1428,37 +1708,37 @@ def test_absolute_request_url(self, client: AsyncKnockMgmt) -> None: ), ) assert request.url == "https://myapi.com/foo" + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - await self.client.get("/foo", cast_to=Model) + await async_client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) @@ -1472,7 +1752,6 @@ async def test_client_max_retries_validation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: class Model(BaseModel): name: str @@ -1484,11 +1763,16 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=False) + non_strict_client = AsyncKnockMgmt( + base_url=base_url, service_token=service_token, _strict_response_validation=False + ) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1511,19 +1795,17 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - @pytest.mark.asyncio - async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncKnockMgmt(base_url=base_url, service_token=service_token, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncKnockMgmt + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = client._calculate_retry_timeout(remaining_retries, options, headers) + calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("knock_mapi._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, @@ -1555,7 +1837,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("knock_mapi._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_omit_retry_count_header( self, async_client: AsyncKnockMgmt, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1581,7 +1862,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("knock_mapi._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_overwrite_retry_count_header( self, async_client: AsyncKnockMgmt, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1604,47 +1884,63 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" - def test_get_platform(self) -> None: - # A previous implementation of asyncify could leave threads unterminated when - # used with nest_asyncio. - # - # Since nest_asyncio.apply() is global and cannot be un-applied, this - # test is run in a separate process to avoid affecting other tests. - test_code = dedent(""" - import asyncio - import nest_asyncio - import threading - - from knock_mapi._utils import asyncify - from knock_mapi._base_client import get_platform - - async def test_main() -> None: - result = await asyncify(get_platform)() - print(result) - for thread in threading.enumerate(): - print(thread.name) - - nest_asyncio.apply() - asyncio.run(test_main()) - """) - with subprocess.Popen( - [sys.executable, "-c", test_code], - text=True, - ) as process: - timeout = 10 # seconds - - start_time = time.monotonic() - while True: - return_code = process.poll() - if return_code is not None: - if return_code != 0: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - - # success - break - - if time.monotonic() - start_time > timeout: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") - - time.sleep(0.1) + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncKnockMgmt) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await async_client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" diff --git a/tests/test_models.py b/tests/test_models.py index 0bb400d4..f9af0398 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone from typing_extensions import Literal, Annotated, TypeAliasType @@ -8,8 +8,8 @@ from pydantic import Field from knock_mapi._utils import PropertyInfo -from knock_mapi._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from knock_mapi._models import BaseModel, construct_type +from knock_mapi._compat import PYDANTIC_V1, parse_obj, model_dump, model_json +from knock_mapi._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -294,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -531,7 +531,7 @@ class Model2(BaseModel): assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} assert m4.to_dict(mode="json") == {"created_at": time_str} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -556,7 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -580,10 +580,10 @@ class Model(BaseModel): assert json.loads(m.to_json()) == {"FOO": "hello"} assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - if PYDANTIC_V2: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - else: + if PYDANTIC_V1: assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' m2 = Model() assert json.loads(m2.to_json()) == {} @@ -595,7 +595,7 @@ class Model(BaseModel): assert json.loads(m3.to_json()) == {"FOO": None} assert json.loads(m3.to_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_json(warnings=False) @@ -622,7 +622,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -679,12 +679,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_unknown_variant() -> None: @@ -768,12 +768,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.foo_type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: @@ -809,7 +809,7 @@ class B(BaseModel): UnionType = cast(Any, Union[A, B]) - assert not hasattr(UnionType, "__discriminator__") + assert not DISCRIMINATOR_CACHE.get(UnionType) m = construct_type( value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) @@ -818,7 +818,7 @@ class B(BaseModel): assert m.type == "b" assert m.data == "foo" # type: ignore[comparison-overlap] - discriminator = UnionType.__discriminator__ + discriminator = DISCRIMINATOR_CACHE.get(UnionType) assert discriminator is not None m = construct_type( @@ -830,10 +830,10 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache - assert UnionType.__discriminator__ is discriminator + assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: Alias = TypeAliasType("Alias", str) # pyright: ignore @@ -849,7 +849,7 @@ class Model(BaseModel): assert m.union == "bar" -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_field_named_cls() -> None: class Model(BaseModel): cls: str @@ -889,3 +889,75 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") +def test_extra_properties() -> None: + class Item(BaseModel): + prop: int + + class Model(BaseModel): + __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + other: str + + if TYPE_CHECKING: + + def __getattr__(self, attr: str) -> Item: ... + + model = construct_type( + type_=Model, + value={ + "a": {"prop": 1}, + "other": "foo", + }, + ) + assert isinstance(model, Model) + assert model.a.prop == 1 + assert isinstance(model.a, Item) + assert model.other == "foo" diff --git a/tests/test_transform.py b/tests/test_transform.py index 6db6a296..d019655c 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,14 +8,14 @@ import pytest -from knock_mapi._types import NOT_GIVEN, Base64FileInput +from knock_mapi._types import Base64FileInput, omit, not_given from knock_mapi._utils import ( PropertyInfo, transform as _transform, parse_datetime, async_transform as _async_transform, ) -from knock_mapi._compat import PYDANTIC_V2 +from knock_mapi._compat import PYDANTIC_V1 from knock_mapi._models import BaseModel _T = TypeVar("_T") @@ -189,7 +189,7 @@ class DateModel(BaseModel): @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - tz = "Z" if PYDANTIC_V2 else "+00:00" + tz = "+00:00" if PYDANTIC_V1 else "Z" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] @@ -297,11 +297,11 @@ async def test_pydantic_unknown_field(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": True} @@ -309,11 +309,11 @@ async def test_pydantic_mismatched_types(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": {"hello": "world"}} @@ -450,4 +450,11 @@ async def test_transform_skipping(use_async: bool) -> None: @pytest.mark.asyncio async def test_strips_notgiven(use_async: bool) -> None: assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} - assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 00000000..57abb5b0 --- /dev/null +++ b/tests/test_utils/test_datetime_parse.py @@ -0,0 +1,110 @@ +""" +Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py +with modifications so it works without pydantic v1 imports. +""" + +from typing import Type, Union +from datetime import date, datetime, timezone, timedelta + +import pytest + +from knock_mapi._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 00000000..093cdb45 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from knock_mapi import _compat +from knock_mapi._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 9b4a45a2..284f8b89 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) diff --git a/tests/utils.py b/tests/utils.py index a6099c6c..6ec99c88 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type @@ -15,10 +15,11 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, is_type_alias_type, ) -from knock_mapi._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from knock_mapi._compat import PYDANTIC_V1, field_outer_type, get_model_fields from knock_mapi._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -27,12 +28,12 @@ def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), @@ -71,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: