diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f6dc2..3582e9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/prelude-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 @@ -30,8 +31,9 @@ jobs: run: ./scripts/lint test: + timeout-minutes: 10 name: test - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'stainless-sdks/prelude-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 418704f..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,237 +0,0 @@ -# Changelog - -## 0.3.0 (2025-04-11) - -Full Changelog: [v0.2.0...v0.3.0](https://github.com/prelude-so/python-sdk/compare/v0.2.0...v0.3.0) - -### Features - -* **api:** update via SDK Studio ([e8db40d](https://github.com/prelude-so/python-sdk/commit/e8db40d0c6bb7ed120d01c7a5133e84611fa2dc5)) -* **api:** update via SDK Studio ([2738f74](https://github.com/prelude-so/python-sdk/commit/2738f749089da145689c78aabdedf810d3329826)) - - -### Bug Fixes - -* **ci:** ensure pip is always available ([#81](https://github.com/prelude-so/python-sdk/issues/81)) ([3496a08](https://github.com/prelude-so/python-sdk/commit/3496a088c4a51ff9755df7d5537031d2b66224b8)) -* **ci:** remove publishing patch ([#82](https://github.com/prelude-so/python-sdk/issues/82)) ([00fa879](https://github.com/prelude-so/python-sdk/commit/00fa8799dc14bc3d2dae941485f2e3a24bfb2bf3)) -* **perf:** optimize some hot paths ([6203988](https://github.com/prelude-so/python-sdk/commit/6203988ff6273cfe5135ec7d427c620a1094f6a1)) -* **perf:** skip traversing types for NotGiven values ([e5a8fd5](https://github.com/prelude-so/python-sdk/commit/e5a8fd59dd7168e68ff026e9d11d796e3d002241)) -* **types:** handle more discriminated union shapes ([#80](https://github.com/prelude-so/python-sdk/issues/80)) ([716195b](https://github.com/prelude-so/python-sdk/commit/716195b1874b4ec76cd39465810e3500c756eae8)) - - -### Chores - -* fix typos ([#83](https://github.com/prelude-so/python-sdk/issues/83)) ([ab98ad3](https://github.com/prelude-so/python-sdk/commit/ab98ad32961298cf1a2f47e6b3cc66a9f69cddbc)) -* **internal:** bump rye to 0.44.0 ([#78](https://github.com/prelude-so/python-sdk/issues/78)) ([436ceca](https://github.com/prelude-so/python-sdk/commit/436ceca01c22fd4015010d5bd3852ce319d0ed65)) -* **internal:** codegen related update ([#79](https://github.com/prelude-so/python-sdk/issues/79)) ([e5e9c6d](https://github.com/prelude-so/python-sdk/commit/e5e9c6d643232cad96a285dd7dc662f98684fbdc)) -* **internal:** expand CI branch coverage ([#87](https://github.com/prelude-so/python-sdk/issues/87)) ([3edb1aa](https://github.com/prelude-so/python-sdk/commit/3edb1aab16d2b38705d64969e9ac70fe00951ee6)) -* **internal:** reduce CI branch coverage ([70118ea](https://github.com/prelude-so/python-sdk/commit/70118ea5611c2f4337b21ac7da52a740bc52d7ff)) -* **internal:** remove extra empty newlines ([#76](https://github.com/prelude-so/python-sdk/issues/76)) ([3e52319](https://github.com/prelude-so/python-sdk/commit/3e5231901ad7bcc6e06a4c82aeaa619f759434f7)) -* **internal:** remove trailing character ([#84](https://github.com/prelude-so/python-sdk/issues/84)) ([526b990](https://github.com/prelude-so/python-sdk/commit/526b990f47cf42a44064d85ed2d8f9acbe35a609)) -* **internal:** slight transform perf improvement ([#85](https://github.com/prelude-so/python-sdk/issues/85)) ([b77e93b](https://github.com/prelude-so/python-sdk/commit/b77e93ba797273dd8a145183d9b9c712659163cd)) -* **tests:** improve enum examples ([#86](https://github.com/prelude-so/python-sdk/issues/86)) ([140d696](https://github.com/prelude-so/python-sdk/commit/140d6966a00666b4ade42fef7de7ec701cf697d3)) - -## 0.2.0 (2025-03-11) - -Full Changelog: [v0.1.0...v0.2.0](https://github.com/prelude-so/python-sdk/compare/v0.1.0...v0.2.0) - -### Features - -* **api:** update via SDK Studio ([#74](https://github.com/prelude-so/python-sdk/issues/74)) ([f9658f1](https://github.com/prelude-so/python-sdk/commit/f9658f1ebacf25f72ae9a8e9076958055c2de570)) -* **client:** allow passing `NotGiven` for body ([#64](https://github.com/prelude-so/python-sdk/issues/64)) ([b32f989](https://github.com/prelude-so/python-sdk/commit/b32f98934973c8c2cfacd3ad9a6c0817405ec3c9)) -* **client:** send `X-Stainless-Read-Timeout` header ([#59](https://github.com/prelude-so/python-sdk/issues/59)) ([6dcc82a](https://github.com/prelude-so/python-sdk/commit/6dcc82a592bdad9316eae8ab7b93095d2176caf3)) - - -### Bug Fixes - -* **client:** mark some request bodies as optional ([b32f989](https://github.com/prelude-so/python-sdk/commit/b32f98934973c8c2cfacd3ad9a6c0817405ec3c9)) - - -### Chores - -* **docs:** update client docstring ([#70](https://github.com/prelude-so/python-sdk/issues/70)) ([61cec66](https://github.com/prelude-so/python-sdk/commit/61cec666606b1999db0d6c7bc08e77aa2aed869e)) -* **internal:** bummp ruff dependency ([#58](https://github.com/prelude-so/python-sdk/issues/58)) ([2381d4a](https://github.com/prelude-so/python-sdk/commit/2381d4a22cfd032f97470e0d012b6fc8a133305c)) -* **internal:** change default timeout to an int ([#56](https://github.com/prelude-so/python-sdk/issues/56)) ([160f11e](https://github.com/prelude-so/python-sdk/commit/160f11e767ab3d5f7f1fdd8e423a6277a651fc82)) -* **internal:** codegen related update ([#63](https://github.com/prelude-so/python-sdk/issues/63)) ([0516484](https://github.com/prelude-so/python-sdk/commit/05164849027af87dba0911340086b7904a7171f2)) -* **internal:** codegen related update ([#67](https://github.com/prelude-so/python-sdk/issues/67)) ([32798a9](https://github.com/prelude-so/python-sdk/commit/32798a95e57769f4fc29abac8ba2dcd58d55a6ef)) -* **internal:** codegen related update ([#68](https://github.com/prelude-so/python-sdk/issues/68)) ([f921517](https://github.com/prelude-so/python-sdk/commit/f921517c1c0b0c8197886f6948cecf56a1fdea87)) -* **internal:** codegen related update ([#71](https://github.com/prelude-so/python-sdk/issues/71)) ([ec7fd9f](https://github.com/prelude-so/python-sdk/commit/ec7fd9feb6f45caf98d9667072910b6a0ebfc25d)) -* **internal:** fix devcontainers setup ([#65](https://github.com/prelude-so/python-sdk/issues/65)) ([da3f6c6](https://github.com/prelude-so/python-sdk/commit/da3f6c6f48241dfe0909aabeb3eec2ba83c0e8ef)) -* **internal:** fix type traversing dictionary params ([#60](https://github.com/prelude-so/python-sdk/issues/60)) ([9bf6b95](https://github.com/prelude-so/python-sdk/commit/9bf6b958c8b1ac01d191fb3ffdad7beb9ad0f06a)) -* **internal:** minor type handling changes ([#61](https://github.com/prelude-so/python-sdk/issues/61)) ([0639a28](https://github.com/prelude-so/python-sdk/commit/0639a28c925209b6d2adb2d3022f350044bf5995)) -* **internal:** properly set __pydantic_private__ ([#66](https://github.com/prelude-so/python-sdk/issues/66)) ([affe056](https://github.com/prelude-so/python-sdk/commit/affe056afdc01fc46d7dc23a003b69bb8528c16d)) -* **internal:** update client tests ([#62](https://github.com/prelude-so/python-sdk/issues/62)) ([6096c2a](https://github.com/prelude-so/python-sdk/commit/6096c2aff213dca771b4e8f8675569e1bc1d1edf)) - - -### Documentation - -* revise readme docs about nested params ([#72](https://github.com/prelude-so/python-sdk/issues/72)) ([bff24a7](https://github.com/prelude-so/python-sdk/commit/bff24a785fbd56e126249cbfbc8f2af5c179b8a6)) -* update URLs from stainlessapi.com to stainless.com ([#69](https://github.com/prelude-so/python-sdk/issues/69)) ([f3c2dc7](https://github.com/prelude-so/python-sdk/commit/f3c2dc7a219c04490aa22cdab677410136dc09d3)) - -## 0.1.0 (2025-02-05) - -Full Changelog: [v0.1.0-beta.1...v0.1.0](https://github.com/prelude-so/python-sdk/compare/v0.1.0-beta.1...v0.1.0) - -### Features - -* **api:** update via SDK Studio ([#54](https://github.com/prelude-so/python-sdk/issues/54)) ([882a265](https://github.com/prelude-so/python-sdk/commit/882a265dbfd660fa86be9fceafd3bf095f332a7f)) - - -### Bug Fixes - -* **tests:** make test_get_platform less flaky ([#50](https://github.com/prelude-so/python-sdk/issues/50)) ([97fe150](https://github.com/prelude-so/python-sdk/commit/97fe150523ae369a3c1729c8a64ddcdbc2660fc6)) - - -### Chores - -* **internal:** avoid pytest-asyncio deprecation warning ([#51](https://github.com/prelude-so/python-sdk/issues/51)) ([0730cb0](https://github.com/prelude-so/python-sdk/commit/0730cb06e3db10b3a2ab537a23eafbe469e6b316)) -* **internal:** bump pyright dependency ([#47](https://github.com/prelude-so/python-sdk/issues/47)) ([4fade6c](https://github.com/prelude-so/python-sdk/commit/4fade6ce965301b6fd4285c6192ce81b078296d6)) -* **internal:** minor formatting changes ([#53](https://github.com/prelude-so/python-sdk/issues/53)) ([a2296ef](https://github.com/prelude-so/python-sdk/commit/a2296ef6d9581d4557832e42d683ec6a503b9b2e)) -* **internal:** minor style changes ([#52](https://github.com/prelude-so/python-sdk/issues/52)) ([04f378c](https://github.com/prelude-so/python-sdk/commit/04f378c43cdcc795ca4a4967075db0f1480bf358)) - - -### Documentation - -* **raw responses:** fix duplicate `the` ([#49](https://github.com/prelude-so/python-sdk/issues/49)) ([b76a3a0](https://github.com/prelude-so/python-sdk/commit/b76a3a0e0376777859a1e938015309fc569734b0)) - -## 0.1.0-beta.1 (2025-01-14) - -Full Changelog: [v0.1.0-alpha.7...v0.1.0-beta.1](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.7...v0.1.0-beta.1) - -### Features - -* **api:** update via SDK Studio ([#45](https://github.com/prelude-so/python-sdk/issues/45)) ([214aa99](https://github.com/prelude-so/python-sdk/commit/214aa996f4ffea2c40030b938f205562d608c144)) - - -### Bug Fixes - -* correctly handle deserialising `cls` fields ([#42](https://github.com/prelude-so/python-sdk/issues/42)) ([6511235](https://github.com/prelude-so/python-sdk/commit/65112351b9c6767bbd3447b17ebab220bf04e34d)) - - -### Chores - -* **internal:** update deps ([#44](https://github.com/prelude-so/python-sdk/issues/44)) ([3c88900](https://github.com/prelude-so/python-sdk/commit/3c8890016b22448529967e11b9082721f8c5954c)) - -## 0.1.0-alpha.7 (2025-01-08) - -Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.6...v0.1.0-alpha.7) - -### Features - -* **api:** update via SDK Studio ([#40](https://github.com/prelude-so/python-sdk/issues/40)) ([a16ad08](https://github.com/prelude-so/python-sdk/commit/a16ad08cc54132be9f0d3553b21d98ca983edcba)) - - -### Bug Fixes - -* **client:** only call .close() when needed ([#37](https://github.com/prelude-so/python-sdk/issues/37)) ([9d64934](https://github.com/prelude-so/python-sdk/commit/9d64934403b4cfa4becd15b3d4f9934354ce1135)) - - -### Chores - -* add missing isclass check ([#35](https://github.com/prelude-so/python-sdk/issues/35)) ([09b83f5](https://github.com/prelude-so/python-sdk/commit/09b83f50fd71367bee93758a31cd53621ba56ab3)) -* **internal:** add support for TypeAliasType ([#31](https://github.com/prelude-so/python-sdk/issues/31)) ([a734093](https://github.com/prelude-so/python-sdk/commit/a7340937bba33d659b17e001c3dc98d7f4b8d009)) -* **internal:** bump httpx dependency ([#36](https://github.com/prelude-so/python-sdk/issues/36)) ([39a4778](https://github.com/prelude-so/python-sdk/commit/39a4778690edc67b96e4b7a67e8d75b3d184502a)) -* **internal:** bump pyright ([#29](https://github.com/prelude-so/python-sdk/issues/29)) ([d10ba94](https://github.com/prelude-so/python-sdk/commit/d10ba94c17a48a69d6de2551f91d417dfd1a14e2)) -* **internal:** codegen related update ([#32](https://github.com/prelude-so/python-sdk/issues/32)) ([f68da06](https://github.com/prelude-so/python-sdk/commit/f68da06af9e1db5524256ee91adeb46746e2d9ce)) -* **internal:** codegen related update ([#34](https://github.com/prelude-so/python-sdk/issues/34)) ([9ac58c5](https://github.com/prelude-so/python-sdk/commit/9ac58c502a1247208c6bc77d482f87b6389dd1a9)) -* **internal:** codegen related update ([#39](https://github.com/prelude-so/python-sdk/issues/39)) ([655d237](https://github.com/prelude-so/python-sdk/commit/655d237f79cf048671378fdfa340b9c32cc36484)) -* **internal:** fix some typos ([#33](https://github.com/prelude-so/python-sdk/issues/33)) ([f720959](https://github.com/prelude-so/python-sdk/commit/f72095954c5f648759c52261916944c4363f2614)) - - -### Documentation - -* fix typos ([#38](https://github.com/prelude-so/python-sdk/issues/38)) ([84b1be6](https://github.com/prelude-so/python-sdk/commit/84b1be6c4450fbed3d9436dc48ad22a10ec75e0d)) - -## 0.1.0-alpha.6 (2024-12-11) - -Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.5...v0.1.0-alpha.6) - -### Features - -* **api:** update via SDK Studio ([#27](https://github.com/prelude-so/python-sdk/issues/27)) ([e34eb1c](https://github.com/prelude-so/python-sdk/commit/e34eb1c2f450d5068cce21d414d3a4a04c3b0766)) - - -### Chores - -* **internal:** bump pydantic dependency ([#25](https://github.com/prelude-so/python-sdk/issues/25)) ([62168ae](https://github.com/prelude-so/python-sdk/commit/62168aeb00400db101ef33e54b8c3d8446bea8f4)) -* make the `Omit` type public ([#23](https://github.com/prelude-so/python-sdk/issues/23)) ([b8aa425](https://github.com/prelude-so/python-sdk/commit/b8aa425a77c73290a46dba106cb7277e67d1bf0f)) - - -### Documentation - -* **readme:** fix http client proxies example ([#26](https://github.com/prelude-so/python-sdk/issues/26)) ([66ce358](https://github.com/prelude-so/python-sdk/commit/66ce358d153fef76d64827abe99907badb64d220)) - -## 0.1.0-alpha.5 (2024-12-03) - -Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.4...v0.1.0-alpha.5) - -### Bug Fixes - -* **client:** compat with new httpx 0.28.0 release ([#20](https://github.com/prelude-so/python-sdk/issues/20)) ([2920a25](https://github.com/prelude-so/python-sdk/commit/2920a25772e640ee100d332658b79836ae0e6375)) - - -### Chores - -* **internal:** bump pyright ([#21](https://github.com/prelude-so/python-sdk/issues/21)) ([3af0b97](https://github.com/prelude-so/python-sdk/commit/3af0b979ddee008fc9dabd2a3d7ce78727df3e03)) -* **internal:** exclude mypy from running on tests ([#18](https://github.com/prelude-so/python-sdk/issues/18)) ([375162d](https://github.com/prelude-so/python-sdk/commit/375162d3e52d77faeaf3970a531a32ef60a50815)) - -## 0.1.0-alpha.4 (2024-11-27) - -Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.3...v0.1.0-alpha.4) - -### Features - -* **api:** update via SDK Studio ([#12](https://github.com/prelude-so/python-sdk/issues/12)) ([380ea22](https://github.com/prelude-so/python-sdk/commit/380ea22a509deeb05b9b27af7b21aae5a70b4380)) -* **api:** update via SDK Studio ([#16](https://github.com/prelude-so/python-sdk/issues/16)) ([a885d0a](https://github.com/prelude-so/python-sdk/commit/a885d0a4aaa978adf582c8743011765dfc65614f)) - - -### Chores - -* **internal:** fix compat model_dump method when warnings are passed ([#13](https://github.com/prelude-so/python-sdk/issues/13)) ([7f9b088](https://github.com/prelude-so/python-sdk/commit/7f9b08842698d0eb6911464089583d56db63e0cf)) -* rebuild project due to codegen change ([#10](https://github.com/prelude-so/python-sdk/issues/10)) ([afd8c51](https://github.com/prelude-so/python-sdk/commit/afd8c5127bce604ba78290aaf62659a3c02471a5)) -* remove now unused `cached-property` dep ([#15](https://github.com/prelude-so/python-sdk/issues/15)) ([292303e](https://github.com/prelude-so/python-sdk/commit/292303e362071f1ba1d7ce2e8311653e4c4ec3f6)) - - -### Documentation - -* add info log level to readme ([#14](https://github.com/prelude-so/python-sdk/issues/14)) ([ee4b1b2](https://github.com/prelude-so/python-sdk/commit/ee4b1b2cbfbec80d0ec43cb8e7c54cd0acaad7b9)) - -## 0.1.0-alpha.3 (2024-11-14) - -Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) - -### Chores - -* update SDK settings ([#7](https://github.com/prelude-so/python-sdk/issues/7)) ([ea056d4](https://github.com/prelude-so/python-sdk/commit/ea056d4e561e83d012c758ffcc5b8b60c0be28f5)) - -## 0.1.0-alpha.2 (2024-11-14) - -Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/prelude-so/python-sdk/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) - -### Features - -* **api:** update via SDK Studio ([#4](https://github.com/prelude-so/python-sdk/issues/4)) ([5dd93b2](https://github.com/prelude-so/python-sdk/commit/5dd93b2620abcec8e912c4c7019edaf6265b62a2)) - -## 0.1.0-alpha.1 (2024-11-13) - -Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/prelude-so/python-sdk/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) - -### Features - -* **api:** update via SDK Studio ([a12fb38](https://github.com/prelude-so/python-sdk/commit/a12fb38d4c48b0719866317a5255082e24e10924)) -* **api:** update via SDK Studio ([6c7b477](https://github.com/prelude-so/python-sdk/commit/6c7b477531e451af0b8d2026439d90cf86927ba5)) -* **api:** update via SDK Studio ([c4e8e64](https://github.com/prelude-so/python-sdk/commit/c4e8e6495a0144ee4cc15acef97dd19ec620a983)) -* **api:** update via SDK Studio ([414794b](https://github.com/prelude-so/python-sdk/commit/414794bb59f6f7369e284c6d2397876af966654e)) -* **api:** update via SDK Studio ([ceb12f0](https://github.com/prelude-so/python-sdk/commit/ceb12f0e846918722f0dd0a95f3621a933f865f1)) -* **api:** update via SDK Studio ([7215a73](https://github.com/prelude-so/python-sdk/commit/7215a73eb5de3eb54c8b1fc061aa364858489dcf)) -* **api:** update via SDK Studio ([c037337](https://github.com/prelude-so/python-sdk/commit/c0373374764304163e5b7de137408c88d35e7dc8)) -* **api:** update via SDK Studio ([6e40de2](https://github.com/prelude-so/python-sdk/commit/6e40de26cdbc73a6e3bdff03e346dc34510d2eb3)) -* **api:** update via SDK Studio ([e9fad9f](https://github.com/prelude-so/python-sdk/commit/e9fad9f7cd6638ef39b288d8f7d111a78d29fa43)) -* **api:** update via SDK Studio ([85a6e9d](https://github.com/prelude-so/python-sdk/commit/85a6e9d7a923856748323abbe9c626dcff6f99bc)) - - -### Chores - -* configure new SDK language ([57c88b9](https://github.com/prelude-so/python-sdk/commit/57c88b95e0cbd5d6ea76e24019145ab5ed173438)) -* go live ([#2](https://github.com/prelude-so/python-sdk/issues/2)) ([3109229](https://github.com/prelude-so/python-sdk/commit/3109229a622a9968d7d97709ccfedf84f4676335)) -* rebuild project due to codegen change ([17c7b8a](https://github.com/prelude-so/python-sdk/commit/17c7b8a6dbc66b3c1db8560dee6633529e22bb0a)) diff --git a/pyproject.toml b/pyproject.toml index b609f29..bdc8d57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/prelude-so/python-sdk" managed = true # version pins are in requirements-dev.lock dev-dependencies = [ - "pyright>=1.1.359", + "pyright==1.1.399", "mypy", "respx", "pytest", @@ -147,6 +147,7 @@ exclude = [ ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false diff --git a/requirements-dev.lock b/requirements-dev.lock index a35ea0c..4908dc6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.392.post0 +pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/prelude_python_sdk/_base_client.py b/src/prelude_python_sdk/_base_client.py index b034c0c..414dcd7 100644 --- a/src/prelude_python_sdk/_base_client.py +++ b/src/prelude_python_sdk/_base_client.py @@ -98,7 +98,11 @@ _AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) if TYPE_CHECKING: - from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG else: try: from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT @@ -115,6 +119,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -130,19 +135,30 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, url: URL | NotGiven = NOT_GIVEN, + json: Body | NotGiven = NOT_GIVEN, params: Query | NotGiven = NOT_GIVEN, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -191,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") @@ -408,8 +437,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 headers = httpx.Headers(headers_dict) idempotency_header = self._idempotency_header - if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers: - headers[idempotency_header] = options.idempotency_key or self._idempotency_key() + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key # Don't set these headers if they were already set or removed by the caller. We check # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. @@ -873,7 +902,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], @@ -884,7 +912,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, ) -> ResponseT: ... @@ -894,7 +921,6 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: Type[_StreamT] | None = None, @@ -904,121 +930,109 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - retries_taken: int, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = self._prepare_options(options) - - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - self._prepare_request(request) - - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) - try: - response = self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err - - log.debug( - 'HTTP Response: %s %s "%i %s" %s', - request.method, - request.url, - response.status_code, - response.reason_phrase, - response.headers, - ) + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - err.response.read() + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return self._process_response( cast_to=cast_to, options=options, @@ -1028,37 +1042,20 @@ def _request( retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) - # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a - # different thread if necessary. time.sleep(timeout) - return self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1402,7 +1399,6 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, ) -> ResponseT: ... @overload @@ -1413,7 +1409,6 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, ) -> _AsyncStreamT: ... @overload @@ -1424,7 +1419,6 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, ) -> ResponseT | _AsyncStreamT: ... async def request( @@ -1434,116 +1428,111 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - if remaining_retries is not None: - retries_taken = options.get_max_retries(self.max_retries) - remaining_retries - else: - retries_taken = 0 - - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - retries_taken=retries_taken, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - retries_taken: int, ) -> ResponseT | _AsyncStreamT: if self._platform is None: # `get_platform` can make blocking IO calls so we # execute it earlier while we are in an async context self._platform = await asyncify(get_platform)() + cast_to = self._maybe_override_cast_to(cast_to, options) + # create a copy of the options we were given so that if the # options are mutated later & we then retry, the retries are # given the original options input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() - cast_to = self._maybe_override_cast_to(cast_to, options) - options = await self._prepare_options(options) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken - request = self._build_request(options, retries_taken=retries_taken) - await self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) - kwargs: HttpxSendArgs = {} - if self.custom_auth is not None: - kwargs["auth"] = self.custom_auth + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) - try: - response = await self._client.send( - request, - stream=stream or self._should_stream_response_body(request=request), - **kwargs, - ) - except httpx.TimeoutException as err: - log.debug("Encountered httpx.TimeoutException", exc_info=True) - - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - log.debug("Raising timeout error") - raise APITimeoutError(request=request) from err - except Exception as err: - log.debug("Encountered Exception", exc_info=True) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - if remaining_retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - stream=stream, - stream_cls=stream_cls, - response_headers=None, + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue - log.debug( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() - try: - response.raise_for_status() - except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code - log.debug("Encountered httpx.HTTPStatusError", exc_info=True) - - if remaining_retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries_taken=retries_taken, - response_headers=err.response.headers, - stream=stream, - stream_cls=stream_cls, - ) + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None - # If the response is streamed then we need to explicitly read the response - # to completion before attempting to access the response text. - if not err.response.is_closed: - await err.response.aread() - - log.debug("Re-raising status error") - raise self._make_status_error_from_response(err.response) from None + break + assert response is not None, "could not resolve response (should never happen)" return await self._process_response( cast_to=cast_to, options=options, @@ -1553,35 +1542,20 @@ async def _request( retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - *, - retries_taken: int, - response_headers: httpx.Headers | None, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining_retries = options.get_max_retries(self.max_retries) - retries_taken + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken if remaining_retries == 1: log.debug("1 retry left") else: log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers) + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) log.info("Retrying request to %s in %f seconds", options.url, timeout) await anyio.sleep(timeout) - return await self._request( - options=options, - cast_to=cast_to, - retries_taken=retries_taken + 1, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, diff --git a/src/prelude_python_sdk/_client.py b/src/prelude_python_sdk/_client.py index fae0d87..133224b 100644 --- a/src/prelude_python_sdk/_client.py +++ b/src/prelude_python_sdk/_client.py @@ -19,10 +19,7 @@ ProxiesTypes, RequestOptions, ) -from ._utils import ( - is_given, - get_async_library, -) +from ._utils import is_given, get_async_library from ._version import __version__ from .resources import watch, lookup, verification, transactional from ._streaming import Stream as Stream, AsyncStream as AsyncStream diff --git a/src/prelude_python_sdk/_models.py b/src/prelude_python_sdk/_models.py index 3493571..798956f 100644 --- a/src/prelude_python_sdk/_models.py +++ b/src/prelude_python_sdk/_models.py @@ -19,7 +19,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -627,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, # Note: if one variant defines an alias then they all should discriminator_alias = field_info.alias - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + 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 diff --git a/src/prelude_python_sdk/_response.py b/src/prelude_python_sdk/_response.py index d75db2a..e6e260d 100644 --- a/src/prelude_python_sdk/_response.py +++ b/src/prelude_python_sdk/_response.py @@ -235,7 +235,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 content_type, *_ = response.headers.get("content-type", "*").split(";") - if content_type != "application/json": + if not content_type.endswith("json"): if is_basemodel(cast_to): try: data = response.json() diff --git a/src/prelude_python_sdk/_utils/_typing.py b/src/prelude_python_sdk/_utils/_typing.py index 1958820..1bac954 100644 --- a/src/prelude_python_sdk/_utils/_typing.py +++ b/src/prelude_python_sdk/_utils/_typing.py @@ -110,7 +110,7 @@ class MyResponse(Foo[_T]): ``` """ cls = cast(object, get_origin(typ) or typ) - if cls in generic_bases: + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] # we're given the class directly return extract_type_arg(typ, index) diff --git a/src/prelude_python_sdk/_utils/_utils.py b/src/prelude_python_sdk/_utils/_utils.py index e5811bb..ea3cf3f 100644 --- a/src/prelude_python_sdk/_utils/_utils.py +++ b/src/prelude_python_sdk/_utils/_utils.py @@ -72,8 +72,16 @@ def _extract_items( from .._files import assert_is_file_content # We have exhausted the path, return the entry we found. - assert_is_file_content(obj, key=flattened_key) assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for entry in obj: + assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") + files.append((flattened_key + "[]", cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) return [(flattened_key, cast(FileTypes, obj))] index += 1 diff --git a/src/prelude_python_sdk/resources/lookup.py b/src/prelude_python_sdk/resources/lookup.py index 6ce0cc3..a43f058 100644 --- a/src/prelude_python_sdk/resources/lookup.py +++ b/src/prelude_python_sdk/resources/lookup.py @@ -9,10 +9,7 @@ from ..types import lookup_lookup_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/prelude_python_sdk/resources/transactional.py b/src/prelude_python_sdk/resources/transactional.py index 5fac16f..be19ee3 100644 --- a/src/prelude_python_sdk/resources/transactional.py +++ b/src/prelude_python_sdk/resources/transactional.py @@ -8,10 +8,7 @@ from ..types import transactional_send_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/prelude_python_sdk/resources/verification.py b/src/prelude_python_sdk/resources/verification.py index 550d152..8b76670 100644 --- a/src/prelude_python_sdk/resources/verification.py +++ b/src/prelude_python_sdk/resources/verification.py @@ -8,10 +8,7 @@ from ..types import verification_check_params, verification_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/src/prelude_python_sdk/resources/watch.py b/src/prelude_python_sdk/resources/watch.py index b1f2e82..2ea109b 100644 --- a/src/prelude_python_sdk/resources/watch.py +++ b/src/prelude_python_sdk/resources/watch.py @@ -8,10 +8,7 @@ from ..types import watch_predict_params, watch_send_events_params, watch_send_feedbacks_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( diff --git a/tests/conftest.py b/tests/conftest.py index addd262..0bbffb3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from prelude_python_sdk import Prelude, AsyncPrelude if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") diff --git a/tests/test_models.py b/tests/test_models.py index 2a5225a..1b0034a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -492,12 +492,15 @@ class Model(BaseModel): resource_id: Optional[str] = None m = Model.construct() + assert m.resource_id is None assert "resource_id" not in m.model_fields_set m = Model.construct(resource_id=None) + assert m.resource_id is None assert "resource_id" in m.model_fields_set m = Model.construct(resource_id="foo") + assert m.resource_id == "foo" assert "resource_id" in m.model_fields_set @@ -832,7 +835,7 @@ class B(BaseModel): @pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: - Alias = TypeAliasType("Alias", str) + Alias = TypeAliasType("Alias", str) # pyright: ignore class Model(BaseModel): alias: Alias