diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e75..ff261bad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b1..c17fdc16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40293964..35bbf55e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,27 +2,33 @@ name: CI on: push: branches: - - main + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: - branches: - - main - - next + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: + timeout-minutes: 10 name: lint - runs-on: ubuntu-latest - - + runs-on: ${{ github.repository == 'stainless-sdks/open-transit-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v4 + - 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.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Install dependencies @@ -30,19 +36,65 @@ jobs: - name: Run lints run: ./scripts/lint + + build: + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') + timeout-minutes: 10 + name: build + permissions: + contents: read + id-token: write + runs-on: ${{ github.repository == 'stainless-sdks/open-transit-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/open-transit-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/open-transit-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: ubuntu-latest - + runs-on: ${{ github.repository == 'stainless-sdks/open-transit-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: | curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Bootstrap @@ -50,4 +102,3 @@ jobs: - name: Run tests run: ./scripts/test - diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 3e178a67..5da47ec9 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - 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.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Publish to PyPI diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 908ad72c..421021c3 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'OneBusAway/python-sdk' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | diff --git a/.gitignore b/.gitignore index 87797408..3824f4c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .prism.log -.vscode +.stdy.log _dev __pycache__ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3b005e52..cdcf20eb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.10" + ".": "1.23.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index cce1cfaa..1266cd82 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,4 @@ -configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/open-transit%2Fopen-transit-261df351536029839245955df4df7341de315fe2b1d1d6aeb063eaef62bcddc9.yml +configured_endpoints: 29 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/open-transit%2Fopen-transit-30baad9d29b0909d180aae300750a0cd8425b52d7a60ba365b6aa4e5f8da6fab.yml +openapi_spec_hash: 218466af34966d9b08728f107cb3b3b0 +config_hash: 3871f5d21bb38ddd334ec04721dea64d 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 index f2fcfe2b..d43b05ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,1174 @@ # Changelog +## 1.23.0 (2026-03-27) + +Full Changelog: [v1.22.2...v1.23.0](https://github.com/OneBusAway/python-sdk/compare/v1.22.2...v1.23.0) + +### Features + +* **internal:** implement indices array format for query and form serialization ([b5f2cbb](https://github.com/OneBusAway/python-sdk/commit/b5f2cbb5007ec41ed5e29e1efee72347521767db)) + +## 1.22.2 (2026-03-25) + +Full Changelog: [v1.22.1...v1.22.2](https://github.com/OneBusAway/python-sdk/compare/v1.22.1...v1.22.2) + +### Bug Fixes + +* sanitize endpoint path params ([3d04ad1](https://github.com/OneBusAway/python-sdk/commit/3d04ad1d05edb09c8eac32fc6a0752fadf20a04e)) + + +### Chores + +* **ci:** skip lint on metadata-only changes ([dc9ab43](https://github.com/OneBusAway/python-sdk/commit/dc9ab43ae3b46cf4dee88f431b1333d9bf152b58)) +* **internal:** update gitignore ([ad43452](https://github.com/OneBusAway/python-sdk/commit/ad434527afac6ff4640dc8ba70871149a7ecab4e)) +* **tests:** bump steady to v0.19.4 ([7dc10af](https://github.com/OneBusAway/python-sdk/commit/7dc10afd245b84a539d7e37b62d3cb67dcab0407)) +* **tests:** bump steady to v0.19.5 ([d0e4a0f](https://github.com/OneBusAway/python-sdk/commit/d0e4a0fe58e465c809f224ea81f08835be586a77)) +* **tests:** bump steady to v0.19.6 ([b068311](https://github.com/OneBusAway/python-sdk/commit/b06831111127830e923c9e069bcedc089cd319c9)) +* **tests:** bump steady to v0.19.7 ([d9c7629](https://github.com/OneBusAway/python-sdk/commit/d9c762957c1bc68715a3b8f7f91b034d054fa58b)) + + +### Refactors + +* **tests:** switch from prism to steady ([9737588](https://github.com/OneBusAway/python-sdk/commit/9737588d82b10a90ecc22f6c0e9b7918362ff799)) + +## 1.22.1 (2026-03-17) + +Full Changelog: [v1.22.0...v1.22.1](https://github.com/OneBusAway/python-sdk/compare/v1.22.0...v1.22.1) + +### Bug Fixes + +* **deps:** bump minimum typing-extensions version ([13f6b82](https://github.com/OneBusAway/python-sdk/commit/13f6b82ee19b6e51b29e800ce5715f967db07b64)) +* **pydantic:** do not pass `by_alias` unless set ([658f6ca](https://github.com/OneBusAway/python-sdk/commit/658f6ca0c5da679a8a771952e19fbef3654bc66b)) + + +### Chores + +* **internal:** tweak CI branches ([0187280](https://github.com/OneBusAway/python-sdk/commit/0187280015c3dd74afef273cc66cb8892ff8c772)) + +## 1.22.0 (2026-03-14) + +Full Changelog: [v1.21.0...v1.22.0](https://github.com/OneBusAway/python-sdk/compare/v1.21.0...v1.22.0) + +### Features + +* **api:** api update ([9c5ed4a](https://github.com/OneBusAway/python-sdk/commit/9c5ed4a46e17562d8c1f2a80a2e44b4374c09aea)) + +## 1.21.0 (2026-03-10) + +Full Changelog: [v1.20.0...v1.21.0](https://github.com/OneBusAway/python-sdk/compare/v1.20.0...v1.21.0) + +### Features + +* **api:** api update ([9e0b66c](https://github.com/OneBusAway/python-sdk/commit/9e0b66ce6927ef0060c6cb81bafd576915c9803f)) + +## 1.20.0 (2026-03-09) + +Full Changelog: [v1.19.7...v1.20.0](https://github.com/OneBusAway/python-sdk/compare/v1.19.7...v1.20.0) + +### Features + +* **api:** api update ([5f55ef1](https://github.com/OneBusAway/python-sdk/commit/5f55ef1165888a1834d6681042e889f695ce7d54)) +* **api:** api update ([e11aff5](https://github.com/OneBusAway/python-sdk/commit/e11aff5e8aee29546c94b7c555bbe1383a1233b5)) + +## 1.19.7 (2026-03-07) + +Full Changelog: [v1.19.6...v1.19.7](https://github.com/OneBusAway/python-sdk/compare/v1.19.6...v1.19.7) + +### Chores + +* **ci:** skip uploading artifacts on stainless-internal branches ([0192fc9](https://github.com/OneBusAway/python-sdk/commit/0192fc9c9015032655a23bb9217d60488709a277)) + +## 1.19.6 (2026-03-06) + +Full Changelog: [v1.19.5...v1.19.6](https://github.com/OneBusAway/python-sdk/compare/v1.19.5...v1.19.6) + +### Chores + +* **test:** do not count install time for mock server timeout ([7869bca](https://github.com/OneBusAway/python-sdk/commit/7869bca87c7ed3f3ea2944f20a3d3d92abb2b374)) + +## 1.19.5 (2026-02-26) + +Full Changelog: [v1.19.4...v1.19.5](https://github.com/OneBusAway/python-sdk/compare/v1.19.4...v1.19.5) + +### Chores + +* **internal:** make `test_proxy_environment_variables` more resilient to env ([e583264](https://github.com/OneBusAway/python-sdk/commit/e583264585805a1f82d86dc004c34c5eb1bb3bac)) + +## 1.19.4 (2026-02-24) + +Full Changelog: [v1.19.3...v1.19.4](https://github.com/OneBusAway/python-sdk/compare/v1.19.3...v1.19.4) + +### Chores + +* **internal:** add request options to SSE classes ([82eb18f](https://github.com/OneBusAway/python-sdk/commit/82eb18f84a795101b3d593a4d0ca0ffe8c2f61ad)) +* **internal:** make `test_proxy_environment_variables` more resilient ([8d549cc](https://github.com/OneBusAway/python-sdk/commit/8d549cc7fd49514167e74d5c7f6889e7681223bd)) + +## 1.19.3 (2026-02-20) + +Full Changelog: [v1.19.2...v1.19.3](https://github.com/OneBusAway/python-sdk/compare/v1.19.2...v1.19.3) + +### Chores + +* update mock server docs ([71a7ebf](https://github.com/OneBusAway/python-sdk/commit/71a7ebfcc6f43c0b67397498ace5627a6a4f6138)) + +## 1.19.2 (2026-02-13) + +Full Changelog: [v1.19.1...v1.19.2](https://github.com/OneBusAway/python-sdk/compare/v1.19.1...v1.19.2) + +### Chores + +* format all `api.md` files ([a800c7d](https://github.com/OneBusAway/python-sdk/commit/a800c7de6f87bf874032cabc42285ac1f27e4a7b)) +* **internal:** fix lint error on Python 3.14 ([790e84d](https://github.com/OneBusAway/python-sdk/commit/790e84dee02f5b6a1a4a2a5180c641c79ab7e49a)) + +## 1.19.1 (2026-02-10) + +Full Changelog: [v1.19.0...v1.19.1](https://github.com/OneBusAway/python-sdk/compare/v1.19.0...v1.19.1) + +### Chores + +* **internal:** bump dependencies ([62f3fa1](https://github.com/OneBusAway/python-sdk/commit/62f3fa13edd93af703978251b8fab75f831f7c24)) + +## 1.19.0 (2026-01-30) + +Full Changelog: [v1.18.2...v1.19.0](https://github.com/OneBusAway/python-sdk/compare/v1.18.2...v1.19.0) + +### Features + +* **client:** add custom JSON encoder for extended type support ([b86708c](https://github.com/OneBusAway/python-sdk/commit/b86708cb3174d253d5a1165314e2be906fcb7f45)) + +## 1.18.2 (2026-01-24) + +Full Changelog: [v1.18.1...v1.18.2](https://github.com/OneBusAway/python-sdk/compare/v1.18.1...v1.18.2) + +### Chores + +* **ci:** upgrade `actions/github-script` ([b53a0e7](https://github.com/OneBusAway/python-sdk/commit/b53a0e7521f6bd5a5b91ca044ab1170e19f66d9c)) + +## 1.18.1 (2026-01-17) + +Full Changelog: [v1.18.0...v1.18.1](https://github.com/OneBusAway/python-sdk/compare/v1.18.0...v1.18.1) + +### Chores + +* **internal:** update `actions/checkout` version ([a74da99](https://github.com/OneBusAway/python-sdk/commit/a74da998f5ff890a42dee0d2ca3b35e5b0bee99f)) + +## 1.18.0 (2026-01-14) + +Full Changelog: [v1.17.8...v1.18.0](https://github.com/OneBusAway/python-sdk/compare/v1.17.8...v1.18.0) + +### Features + +* **client:** add support for binary request streaming ([65e70c7](https://github.com/OneBusAway/python-sdk/commit/65e70c7ca5821ba92e25975d81f7298e6831962a)) + +## 1.17.8 (2026-01-02) + +Full Changelog: [v1.17.7...v1.17.8](https://github.com/OneBusAway/python-sdk/compare/v1.17.7...v1.17.8) + +### Bug Fixes + +* compat with Python 3.14 ([2ebc518](https://github.com/OneBusAway/python-sdk/commit/2ebc5184554db31387b2f683265720d58dd9bb2f)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([37abc5b](https://github.com/OneBusAway/python-sdk/commit/37abc5b808843321a5011b935590c5d3455f0c27)) +* ensure streams are always closed ([75f1288](https://github.com/OneBusAway/python-sdk/commit/75f1288ddc3202c2fe6850b8f968720ed88cb470)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([f10f373](https://github.com/OneBusAway/python-sdk/commit/f10f3734af08ea934c7b5be9de0cd2af106afaea)) +* use async_to_httpx_files in patch method ([bb271d4](https://github.com/OneBusAway/python-sdk/commit/bb271d4d067c06c8848488a5ba050e89b85fd671)) + + +### Chores + +* add missing docstrings ([5daefdd](https://github.com/OneBusAway/python-sdk/commit/5daefdde179696d3ade876b33b94fb79f5972155)) +* add Python 3.14 classifier and testing ([5ba9679](https://github.com/OneBusAway/python-sdk/commit/5ba9679c37d905d2b994fb3ad033825142ee19e2)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([167648d](https://github.com/OneBusAway/python-sdk/commit/167648d8fb485e522aa49611ee25523190b78c9a)) +* **docs:** use environment variables for authentication in code snippets ([a2ac5df](https://github.com/OneBusAway/python-sdk/commit/a2ac5df1da8f164cbe85325385266b5340fd361b)) +* **internal/tests:** avoid race condition with implicit client cleanup ([f6c9ee8](https://github.com/OneBusAway/python-sdk/commit/f6c9ee8b1713fee3b65a599c98653d6183d6e162)) +* **internal:** add `--fix` argument to lint script ([fecb3ed](https://github.com/OneBusAway/python-sdk/commit/fecb3edeae22300f16eacd4f35da31e2243f17c8)) +* **internal:** add missing files argument to base client ([1744224](https://github.com/OneBusAway/python-sdk/commit/17442243ef6d3ca342ac4294de2257e59fdd7da4)) +* **internal:** codegen related update ([dc918e3](https://github.com/OneBusAway/python-sdk/commit/dc918e3df35585c12b7a05e433826563faccb086)) +* **internal:** grammar fix (it's -> its) ([aa8df91](https://github.com/OneBusAway/python-sdk/commit/aa8df91bb2ba826f49bacf3a2a777579931487d9)) +* **package:** drop Python 3.8 support ([b633259](https://github.com/OneBusAway/python-sdk/commit/b633259385cddf32422b425a174a2a7dbe34021c)) +* speedup initial import ([d938f66](https://github.com/OneBusAway/python-sdk/commit/d938f6643cf144bb8db22d8c89024785ff5fdd97)) +* update lockfile ([4fd805d](https://github.com/OneBusAway/python-sdk/commit/4fd805dfa1c4efe2fd53d7aa46c75e368c97463f)) + +## 1.17.7 (2025-10-30) + +Full Changelog: [v1.17.6...v1.17.7](https://github.com/OneBusAway/python-sdk/compare/v1.17.6...v1.17.7) + +### Bug Fixes + +* **client:** close streams without requiring full consumption ([70f09eb](https://github.com/OneBusAway/python-sdk/commit/70f09ebf5cc0c72556e424744ea305f97cf87cda)) + +## 1.17.6 (2025-10-18) + +Full Changelog: [v1.17.5...v1.17.6](https://github.com/OneBusAway/python-sdk/compare/v1.17.5...v1.17.6) + +### Chores + +* bump `httpx-aiohttp` version to 0.1.9 ([6ae619a](https://github.com/OneBusAway/python-sdk/commit/6ae619af34818d78c53716f91607e57045f503a6)) + +## 1.17.5 (2025-10-11) + +Full Changelog: [v1.17.4...v1.17.5](https://github.com/OneBusAway/python-sdk/compare/v1.17.4...v1.17.5) + +### Chores + +* **internal:** detect missing future annotations with ruff ([9807d04](https://github.com/OneBusAway/python-sdk/commit/9807d04636cb4e62fcd4d825d1c43718d91618ec)) + +## 1.17.4 (2025-09-23) + +Full Changelog: [v1.17.3...v1.17.4](https://github.com/OneBusAway/python-sdk/compare/v1.17.3...v1.17.4) + +### Chores + +* improve example values ([7a14d29](https://github.com/OneBusAway/python-sdk/commit/7a14d29ad55a4bf845d71ea5aee8ae00a4ed3cdc)) + +## 1.17.3 (2025-09-20) + +Full Changelog: [v1.17.2...v1.17.3](https://github.com/OneBusAway/python-sdk/compare/v1.17.2...v1.17.3) + +### Chores + +* do not install brew dependencies in ./scripts/bootstrap by default ([39dcea7](https://github.com/OneBusAway/python-sdk/commit/39dcea7358a13250e6ef328e8885052155a64445)) + +## 1.17.2 (2025-09-19) + +Full Changelog: [v1.17.1...v1.17.2](https://github.com/OneBusAway/python-sdk/compare/v1.17.1...v1.17.2) + +### Chores + +* **internal:** update pydantic dependency ([c13d52b](https://github.com/OneBusAway/python-sdk/commit/c13d52be2ec7ab0ff9da3f2120792d9ed247881e)) +* **types:** change optional parameter type from NotGiven to Omit ([c24d539](https://github.com/OneBusAway/python-sdk/commit/c24d539861b7594e5a88f68de5a422ab0db096e3)) + +## 1.17.1 (2025-09-06) + +Full Changelog: [v1.17.0...v1.17.1](https://github.com/OneBusAway/python-sdk/compare/v1.17.0...v1.17.1) + +### Chores + +* **internal:** move mypy configurations to `pyproject.toml` file ([baa06e6](https://github.com/OneBusAway/python-sdk/commit/baa06e63b06e3082adc11f9a7930276c8777d527)) +* **tests:** simplify `get_platform` test ([0cced81](https://github.com/OneBusAway/python-sdk/commit/0cced81054c3e77548c7b34fe4899ccbbc9efb96)) + +## 1.17.0 (2025-09-04) + +Full Changelog: [v1.16.0...v1.17.0](https://github.com/OneBusAway/python-sdk/compare/v1.16.0...v1.17.0) + +### Features + +* improve future compat with pydantic v3 ([1b63906](https://github.com/OneBusAway/python-sdk/commit/1b63906456bd2aecd39460d9b12bf45a063b7392)) + +## 1.16.0 (2025-09-03) + +Full Changelog: [v1.15.5...v1.16.0](https://github.com/OneBusAway/python-sdk/compare/v1.15.5...v1.16.0) + +### Features + +* **types:** replace List[str] with SequenceNotStr in params ([810c88d](https://github.com/OneBusAway/python-sdk/commit/810c88dfa53496c5d0c0b913cf1944da6de86185)) + +## 1.15.5 (2025-08-30) + +Full Changelog: [v1.15.4...v1.15.5](https://github.com/OneBusAway/python-sdk/compare/v1.15.4...v1.15.5) + +### Chores + +* **internal:** add Sequence related utils ([767a34a](https://github.com/OneBusAway/python-sdk/commit/767a34a8634d836ec05bc577e640df14416dd9c2)) + +## 1.15.4 (2025-08-27) + +Full Changelog: [v1.15.3...v1.15.4](https://github.com/OneBusAway/python-sdk/compare/v1.15.3...v1.15.4) + +### Bug Fixes + +* avoid newer type syntax ([4100abb](https://github.com/OneBusAway/python-sdk/commit/4100abbcad60289bd7e9e8b4215e2e319f82fc55)) + + +### Chores + +* **internal:** update pyright exclude list ([c71f41b](https://github.com/OneBusAway/python-sdk/commit/c71f41b6ae8c1f774d1a70407815f5fb0f34e810)) + +## 1.15.3 (2025-08-26) + +Full Changelog: [v1.15.2...v1.15.3](https://github.com/OneBusAway/python-sdk/compare/v1.15.2...v1.15.3) + +### Chores + +* **internal:** change ci workflow machines ([0225ca8](https://github.com/OneBusAway/python-sdk/commit/0225ca83be2a3f4c8466ab9416340c1bb8919daa)) +* update github action ([f1e8695](https://github.com/OneBusAway/python-sdk/commit/f1e8695c9f221b19dbeb9f0fa0fb9fd1aa9b9959)) + +## 1.15.2 (2025-08-10) + +Full Changelog: [v1.15.1...v1.15.2](https://github.com/OneBusAway/python-sdk/compare/v1.15.1...v1.15.2) + +### Chores + +* **internal:** update comment in script ([b0cd25c](https://github.com/OneBusAway/python-sdk/commit/b0cd25ccb53b773dd3df52aaf01f4d4c7cd48c60)) +* update @stainless-api/prism-cli to v5.15.0 ([af3e4cf](https://github.com/OneBusAway/python-sdk/commit/af3e4cf8c736a9dd2da2b348a55c75721e2537be)) + +## 1.15.1 (2025-08-06) + +Full Changelog: [v1.15.0...v1.15.1](https://github.com/OneBusAway/python-sdk/compare/v1.15.0...v1.15.1) + +### Chores + +* **internal:** fix ruff target version ([5d8b092](https://github.com/OneBusAway/python-sdk/commit/5d8b092137e533550f599dc96b42202c9057d35c)) + +## 1.15.0 (2025-07-31) + +Full Changelog: [v1.14.2...v1.15.0](https://github.com/OneBusAway/python-sdk/compare/v1.14.2...v1.15.0) + +### Features + +* **client:** support file upload requests ([27cab18](https://github.com/OneBusAway/python-sdk/commit/27cab1813ce2774ca1a88856190898d28b303c00)) + +## 1.14.2 (2025-07-25) + +Full Changelog: [v1.14.1...v1.14.2](https://github.com/OneBusAway/python-sdk/compare/v1.14.1...v1.14.2) + +### Chores + +* **project:** add settings file for vscode ([995b6d4](https://github.com/OneBusAway/python-sdk/commit/995b6d4a9aea939ac187f802cf330a0db134997d)) + +## 1.14.1 (2025-07-23) + +Full Changelog: [v1.14.0...v1.14.1](https://github.com/OneBusAway/python-sdk/compare/v1.14.0...v1.14.1) + +### Bug Fixes + +* **parsing:** ignore empty metadata ([eef0db2](https://github.com/OneBusAway/python-sdk/commit/eef0db25134ae51703d4560860a984ddc603e114)) +* **parsing:** parse extra field types ([e477fc8](https://github.com/OneBusAway/python-sdk/commit/e477fc84dbc063d64fc6983e243f0a41a6dfd07c)) + +## 1.14.0 (2025-07-15) + +Full Changelog: [v1.13.5...v1.14.0](https://github.com/OneBusAway/python-sdk/compare/v1.13.5...v1.14.0) + +### Features + +* clean up environment call outs ([ecac1f6](https://github.com/OneBusAway/python-sdk/commit/ecac1f6610cdae3cde3a0f982c4bc11192a5a809)) + +## 1.13.5 (2025-07-12) + +Full Changelog: [v1.13.4...v1.13.5](https://github.com/OneBusAway/python-sdk/compare/v1.13.4...v1.13.5) + +### Bug Fixes + +* **client:** don't send Content-Type header on GET requests ([935e2d2](https://github.com/OneBusAway/python-sdk/commit/935e2d25ec8736de5b30d7653c2e85fd5620a45d)) + + +### Chores + +* **readme:** fix version rendering on pypi ([e29e75d](https://github.com/OneBusAway/python-sdk/commit/e29e75dcc69ffc9b0c434cf556cfa9b5b374d7a4)) + +## 1.13.4 (2025-07-10) + +Full Changelog: [v1.13.3...v1.13.4](https://github.com/OneBusAway/python-sdk/compare/v1.13.3...v1.13.4) + +### Bug Fixes + +* **parsing:** correctly handle nested discriminated unions ([d1bb078](https://github.com/OneBusAway/python-sdk/commit/d1bb078b61f81f9f5192505e08cea3dc7df136f4)) + +## 1.13.3 (2025-07-09) + +Full Changelog: [v1.13.2...v1.13.3](https://github.com/OneBusAway/python-sdk/compare/v1.13.2...v1.13.3) + +### Chores + +* **internal:** bump pinned h11 dep ([495b65d](https://github.com/OneBusAway/python-sdk/commit/495b65dad98321a877258bdd87872357c245f9cf)) +* **package:** mark python 3.13 as supported ([404807c](https://github.com/OneBusAway/python-sdk/commit/404807c5890115889c90bc1236f9ca0b57a426c8)) + +## 1.13.2 (2025-07-05) + +Full Changelog: [v1.13.1...v1.13.2](https://github.com/OneBusAway/python-sdk/compare/v1.13.1...v1.13.2) + +### Chores + +* **internal:** version bump ([b017c04](https://github.com/OneBusAway/python-sdk/commit/b017c04d3b411ec13e528851f32c850839c3274d)) + +## 1.13.1 (2025-07-02) + +Full Changelog: [v1.13.0...v1.13.1](https://github.com/OneBusAway/python-sdk/compare/v1.13.0...v1.13.1) + +### Bug Fixes + +* **ci:** correct conditional ([1b938b2](https://github.com/OneBusAway/python-sdk/commit/1b938b23923b4ee59453b26a896747bd7f4d0386)) + + +### Chores + +* **ci:** change upload type ([add541c](https://github.com/OneBusAway/python-sdk/commit/add541c1c196ef11b5ae310d0944b39f476915b0)) +* **ci:** only run for pushes and fork pull requests ([e15aec6](https://github.com/OneBusAway/python-sdk/commit/e15aec647d9bdbcbd061158740afa8df57f0d41c)) + +## 1.13.0 (2025-06-27) + +Full Changelog: [v1.12.2...v1.13.0](https://github.com/OneBusAway/python-sdk/compare/v1.12.2...v1.13.0) + +### Features + +* **client:** add support for aiohttp ([30eada9](https://github.com/OneBusAway/python-sdk/commit/30eada97ab7083d960307901bb55ef931ff6b202)) + + +### Bug Fixes + +* **ci:** release-doctor — report correct token name ([32bbbb3](https://github.com/OneBusAway/python-sdk/commit/32bbbb36efc1e3ebe55502aba1d140b0670e671c)) + + +### Chores + +* **tests:** skip some failing tests on the latest python versions ([6a88517](https://github.com/OneBusAway/python-sdk/commit/6a88517328513ff342df314e1d1da26795f3bb72)) + +## 1.12.2 (2025-06-19) + +Full Changelog: [v1.12.1...v1.12.2](https://github.com/OneBusAway/python-sdk/compare/v1.12.1...v1.12.2) + +### Bug Fixes + +* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([355779a](https://github.com/OneBusAway/python-sdk/commit/355779aceef53b58275a4dab5020d414044adbb0)) + + +### Chores + +* **ci:** enable for pull requests ([2b9118f](https://github.com/OneBusAway/python-sdk/commit/2b9118f1154e0f8e90c71a1fb37577c63a57c63f)) +* **internal:** update conftest.py ([a2b6c3e](https://github.com/OneBusAway/python-sdk/commit/a2b6c3e1d78656155c5eb2f0a1cff69de045909b)) +* **readme:** update badges ([dc50499](https://github.com/OneBusAway/python-sdk/commit/dc504994e1f2c2c76e0a46941b97b35a90b0e5c5)) +* **tests:** add tests for httpx client instantiation & proxies ([55c4719](https://github.com/OneBusAway/python-sdk/commit/55c4719837747d638cfbb9e79f683189045338ab)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([5e8cb3a](https://github.com/OneBusAway/python-sdk/commit/5e8cb3aa66d4a60f7e96d71cf5bbf2fedeb172ef)) + +## 1.12.1 (2025-06-13) + +Full Changelog: [v1.12.0...v1.12.1](https://github.com/OneBusAway/python-sdk/compare/v1.12.0...v1.12.1) + +### Bug Fixes + +* **client:** correctly parse binary response | stream ([4bc049c](https://github.com/OneBusAway/python-sdk/commit/4bc049ce0a36680434f5570fd38f3ca673250bba)) + + +### Chores + +* **tests:** run tests in parallel ([1329bbe](https://github.com/OneBusAway/python-sdk/commit/1329bbe39e76943fe51efea63dc5ee7f85154135)) + +## 1.12.0 (2025-06-03) + +Full Changelog: [v1.11.1...v1.12.0](https://github.com/OneBusAway/python-sdk/compare/v1.11.1...v1.12.0) + +### Features + +* **client:** add follow_redirects request option ([569c63a](https://github.com/OneBusAway/python-sdk/commit/569c63a79d7d69d4e7f54466860e462114385db9)) + + +### Chores + +* **docs:** remove reference to rye shell ([064f5b8](https://github.com/OneBusAway/python-sdk/commit/064f5b86a6b67b49431886f2892cdfb6cd221e8a)) + +## 1.11.1 (2025-05-22) + +Full Changelog: [v1.11.0...v1.11.1](https://github.com/OneBusAway/python-sdk/compare/v1.11.0...v1.11.1) + +### Chores + +* **docs:** grammar improvements ([052af65](https://github.com/OneBusAway/python-sdk/commit/052af65693c380484e4e920b4a831c52b4345daa)) + +## 1.11.0 (2025-05-20) + +Full Changelog: [v1.10.0...v1.11.0](https://github.com/OneBusAway/python-sdk/compare/v1.10.0...v1.11.0) + +### Features + +* **api:** api update ([9981b15](https://github.com/OneBusAway/python-sdk/commit/9981b15b92023faad9812c2ad55f977673424c1b)) + +## 1.10.0 (2025-05-17) + +Full Changelog: [v1.9.1...v1.10.0](https://github.com/OneBusAway/python-sdk/compare/v1.9.1...v1.10.0) + +### Features + +* **api:** api update ([e1770ea](https://github.com/OneBusAway/python-sdk/commit/e1770ea404333e965948a7e27bec57215985c82d)) + +## 1.9.1 (2025-05-16) + +Full Changelog: [v1.9.0...v1.9.1](https://github.com/OneBusAway/python-sdk/compare/v1.9.0...v1.9.1) + +### Chores + +* **internal:** codegen related update ([d996edf](https://github.com/OneBusAway/python-sdk/commit/d996edfce7df8c642feda6c88a46e3e9638e2364)) + +## 1.9.0 (2025-05-15) + +Full Changelog: [v1.8.6...v1.9.0](https://github.com/OneBusAway/python-sdk/compare/v1.8.6...v1.9.0) + +### Features + +* **client:** support digest authentication ([7968d8e](https://github.com/OneBusAway/python-sdk/commit/7968d8ec213da1e0f7c6006a262f4490d33dc189)) +* **client:** support digest authentication ([645fab1](https://github.com/OneBusAway/python-sdk/commit/645fab1f917aa2911f8a202962c14497703a6e4a)) +* **client:** support digest authentication ([442412c](https://github.com/OneBusAway/python-sdk/commit/442412cf7414b96fcc111b099b0899be6dda2c62)) +* **client:** support digest authentication ([78752fb](https://github.com/OneBusAway/python-sdk/commit/78752fb2e2757500df6fbe2243f8c958d21d45b1)) + + +### Bug Fixes + +* **pydantic v1:** more robust ModelField.annotation check ([0bf5ca2](https://github.com/OneBusAway/python-sdk/commit/0bf5ca29079b4844d824aeffa45701d453aee07f)) +* suppress type checking warnings in stops_for_location.py ([6f86de6](https://github.com/OneBusAway/python-sdk/commit/6f86de60f85548d408891725075ac596b6a756f5)) + + +### Chores + +* broadly detect json family of content-type headers ([4ac9df4](https://github.com/OneBusAway/python-sdk/commit/4ac9df4564811ba3a7874ed2fccdcedf0ee9f926)) +* **ci:** add timeout thresholds for CI jobs ([4f9bf3f](https://github.com/OneBusAway/python-sdk/commit/4f9bf3f56e7a4121c02d0b42940758c5ee8d15a9)) +* **ci:** only use depot for staging repos ([b943cd6](https://github.com/OneBusAway/python-sdk/commit/b943cd6d8a741d5b0eb46f0e3f071c05243d7a96)) +* **client:** minor internal fixes ([46ef2ca](https://github.com/OneBusAway/python-sdk/commit/46ef2ca789d1cda13173d4cb9eb13cd45926436d)) +* formatting ([3a1f625](https://github.com/OneBusAway/python-sdk/commit/3a1f6255316b831eec27a42deecfd7a51d55bb24)) +* **internal:** base client updates ([5dcff0e](https://github.com/OneBusAway/python-sdk/commit/5dcff0e41a90577caf12e799b9388e88661afdaf)) +* **internal:** bump pyright version ([372d33b](https://github.com/OneBusAway/python-sdk/commit/372d33b378a44bc1f7a37282c7c5a6617978b105)) +* **internal:** codegen related update ([f2c26ec](https://github.com/OneBusAway/python-sdk/commit/f2c26ec71b462bee6c57253056c2e35ac26bea59)) +* **internal:** fix list file params ([2401f32](https://github.com/OneBusAway/python-sdk/commit/2401f32e86b394778db413a97be0ece06ea34346)) +* **internal:** import reformatting ([55369d4](https://github.com/OneBusAway/python-sdk/commit/55369d4913d0db14d4f8a5f0b177513e29c49246)) +* **internal:** minor formatting changes ([769fecb](https://github.com/OneBusAway/python-sdk/commit/769fecb626b4d8c82b26d156a5583c635c8471f9)) +* **internal:** refactor retries to not use recursion ([f034ad2](https://github.com/OneBusAway/python-sdk/commit/f034ad2291495dc12c523e297f7871cf4bdccc42)) +* **internal:** update models test ([6d50ec1](https://github.com/OneBusAway/python-sdk/commit/6d50ec101d12998985b8b3559a2ae06d9f9f8f91)) +* **internal:** update pyright settings ([2583fea](https://github.com/OneBusAway/python-sdk/commit/2583feac4ae8ea9a480be9694dd1bbfbf36c8e24)) + +## 1.8.6 (2025-05-15) + +Full Changelog: [v1.8.5...v1.8.6](https://github.com/OneBusAway/python-sdk/compare/v1.8.5...v1.8.6) + +### Bug Fixes + +* **perf:** optimize some hot paths ([ddf06ce](https://github.com/OneBusAway/python-sdk/commit/ddf06ce0964f841e82a46417d233bc309740e169)) +* **perf:** skip traversing types for NotGiven values ([3ef7f7b](https://github.com/OneBusAway/python-sdk/commit/3ef7f7b6a016058d1d68e912d12e81e4c7b8f10f)) + + +### Chores + +* **internal:** codegen related update ([#290](https://github.com/OneBusAway/python-sdk/issues/290)) ([ae2f18c](https://github.com/OneBusAway/python-sdk/commit/ae2f18c4204970d2f5021b0060c6be7c562dfb54)) +* **internal:** expand CI branch coverage ([3ba0a52](https://github.com/OneBusAway/python-sdk/commit/3ba0a52f74ffdc7b0cabdf2dfdd44537c132dc06)) +* **internal:** reduce CI branch coverage ([c2e056e](https://github.com/OneBusAway/python-sdk/commit/c2e056e95068b3ffb80279d69b5a7603ffbc256c)) +* **internal:** slight transform perf improvement ([#291](https://github.com/OneBusAway/python-sdk/issues/291)) ([f74def5](https://github.com/OneBusAway/python-sdk/commit/f74def5e32618dc6ff2fb364cad51c01940af849)) + +## 1.8.5 (2025-05-15) + +Full Changelog: [v1.8.4...v1.8.5](https://github.com/OneBusAway/python-sdk/compare/v1.8.4...v1.8.5) + +### Chores + +* **internal:** codegen related update ([#288](https://github.com/OneBusAway/python-sdk/issues/288)) ([d301a0c](https://github.com/OneBusAway/python-sdk/commit/d301a0cf24104ff0d791046c99093f3c47365bf3)) + +## 1.8.4 (2025-03-27) + +Full Changelog: [v1.8.3...v1.8.4](https://github.com/OneBusAway/python-sdk/compare/v1.8.3...v1.8.4) + +### Bug Fixes + +* **ci:** ensure pip is always available ([#284](https://github.com/OneBusAway/python-sdk/issues/284)) ([959d15e](https://github.com/OneBusAway/python-sdk/commit/959d15edcf912b2742e3598a35396041492ecbb7)) +* **ci:** remove publishing patch ([#285](https://github.com/OneBusAway/python-sdk/issues/285)) ([43691e0](https://github.com/OneBusAway/python-sdk/commit/43691e0b16c2c885b8e75d07e82f1ff26d961ac8)) +* **types:** handle more discriminated union shapes ([#283](https://github.com/OneBusAway/python-sdk/issues/283)) ([d55c08a](https://github.com/OneBusAway/python-sdk/commit/d55c08ad0ff636bf899c7a989185f30f431649bb)) + + +### Chores + +* fix typos ([#286](https://github.com/OneBusAway/python-sdk/issues/286)) ([e45e047](https://github.com/OneBusAway/python-sdk/commit/e45e04798e64b912d4ba598ebc3ef0cdaf031203)) +* **internal:** bump rye to 0.44.0 ([#282](https://github.com/OneBusAway/python-sdk/issues/282)) ([217ce3f](https://github.com/OneBusAway/python-sdk/commit/217ce3f97be80d4861ffc277336ec196c6873788)) +* **internal:** remove extra empty newlines ([#280](https://github.com/OneBusAway/python-sdk/issues/280)) ([3ca5c73](https://github.com/OneBusAway/python-sdk/commit/3ca5c734669aad9252aca2761537f2cf6dc9cfab)) + +## 1.8.3 (2025-03-04) + +Full Changelog: [v1.8.2...v1.8.3](https://github.com/OneBusAway/python-sdk/compare/v1.8.2...v1.8.3) + +### Chores + +* **docs:** update client docstring ([#274](https://github.com/OneBusAway/python-sdk/issues/274)) ([cd68881](https://github.com/OneBusAway/python-sdk/commit/cd68881b35a8a1d83ff1de64bca8e8dea8494ebd)) +* **internal:** remove unused http client options forwarding ([#276](https://github.com/OneBusAway/python-sdk/issues/276)) ([7b2e2e5](https://github.com/OneBusAway/python-sdk/commit/7b2e2e505c7d440daa02464839512ee309113b02)) + + +### Documentation + +* update URLs from stainlessapi.com to stainless.com ([#273](https://github.com/OneBusAway/python-sdk/issues/273)) ([362a2f9](https://github.com/OneBusAway/python-sdk/commit/362a2f95e074ad28da196fc58a10df96ca7bc9e9)) + +## 1.8.2 (2025-02-26) + +Full Changelog: [v1.8.1...v1.8.2](https://github.com/OneBusAway/python-sdk/compare/v1.8.1...v1.8.2) + +### Chores + +* **internal:** properly set __pydantic_private__ ([#270](https://github.com/OneBusAway/python-sdk/issues/270)) ([b14c2b5](https://github.com/OneBusAway/python-sdk/commit/b14c2b5b6f6bf376fcf0aeb6c3ae0536a222c131)) + +## 1.8.1 (2025-02-22) + +Full Changelog: [v1.8.0...v1.8.1](https://github.com/OneBusAway/python-sdk/compare/v1.8.0...v1.8.1) + +### Chores + +* **internal:** fix devcontainers setup ([#267](https://github.com/OneBusAway/python-sdk/issues/267)) ([aaa9050](https://github.com/OneBusAway/python-sdk/commit/aaa9050c599ae62cfa24ba579c5a6b864a7adb73)) + +## 1.8.0 (2025-02-21) + +Full Changelog: [v1.7.1...v1.8.0](https://github.com/OneBusAway/python-sdk/compare/v1.7.1...v1.8.0) + +### Features + +* **client:** allow passing `NotGiven` for body ([#264](https://github.com/OneBusAway/python-sdk/issues/264)) ([a4b3083](https://github.com/OneBusAway/python-sdk/commit/a4b3083b2276f1b14efc2c1dc8067218fade420e)) + + +### Bug Fixes + +* **client:** mark some request bodies as optional ([a4b3083](https://github.com/OneBusAway/python-sdk/commit/a4b3083b2276f1b14efc2c1dc8067218fade420e)) + +## 1.7.1 (2025-02-14) + +Full Changelog: [v1.7.0...v1.7.1](https://github.com/OneBusAway/python-sdk/compare/v1.7.0...v1.7.1) + +### Bug Fixes + +* asyncify on non-asyncio runtimes ([#261](https://github.com/OneBusAway/python-sdk/issues/261)) ([5d5e531](https://github.com/OneBusAway/python-sdk/commit/5d5e5310af6acf2accc87d2d3479bbaf4f90d6e0)) + +## 1.7.0 (2025-02-08) + +Full Changelog: [v1.6.0...v1.7.0](https://github.com/OneBusAway/python-sdk/compare/v1.6.0...v1.7.0) + +### Features + +* chore(tests): formatting ([dbb3cd7](https://github.com/OneBusAway/python-sdk/commit/dbb3cd78e5059a2e34efe2b323376abe7e544ac9)) + +## 1.6.0 (2025-02-08) + +Full Changelog: [v1.5.2...v1.6.0](https://github.com/OneBusAway/python-sdk/compare/v1.5.2...v1.6.0) + +### Features + +* fix(tests): use urllib.parse.quote_plus for API key encoding in expected URLs ([41dfd84](https://github.com/OneBusAway/python-sdk/commit/41dfd84d5c705825fa4b0dea4d52b964b08921d0)) + +## 1.5.2 (2025-02-07) + +Full Changelog: [v1.5.1...v1.5.2](https://github.com/OneBusAway/python-sdk/compare/v1.5.1...v1.5.2) + +### Chores + +* **internal:** minor type handling changes ([#254](https://github.com/OneBusAway/python-sdk/issues/254)) ([401146a](https://github.com/OneBusAway/python-sdk/commit/401146a94806dba8c0aa14252aadd09089c590b9)) + +## 1.5.1 (2025-02-07) + +Full Changelog: [v1.5.0...v1.5.1](https://github.com/OneBusAway/python-sdk/compare/v1.5.0...v1.5.1) + +### Chores + +* **internal:** fix type traversing dictionary params ([#251](https://github.com/OneBusAway/python-sdk/issues/251)) ([6dc1d57](https://github.com/OneBusAway/python-sdk/commit/6dc1d570d4fa8f1391755af3431ae2cdda088d9e)) + +## 1.5.0 (2025-02-06) + +Full Changelog: [v1.4.23...v1.5.0](https://github.com/OneBusAway/python-sdk/compare/v1.4.23...v1.5.0) + +### Features + +* **client:** send `X-Stainless-Read-Timeout` header ([#248](https://github.com/OneBusAway/python-sdk/issues/248)) ([2bdb366](https://github.com/OneBusAway/python-sdk/commit/2bdb366dbde6c2c9ad5e5cd89cf07b24410ca472)) + +## 1.4.23 (2025-02-04) + +Full Changelog: [v1.4.22...v1.4.23](https://github.com/OneBusAway/python-sdk/compare/v1.4.22...v1.4.23) + +### Chores + +* **internal:** bummp ruff dependency ([#245](https://github.com/OneBusAway/python-sdk/issues/245)) ([b8d6007](https://github.com/OneBusAway/python-sdk/commit/b8d6007b83f20484c4172901a262414247712972)) + +## 1.4.22 (2025-02-04) + +Full Changelog: [v1.4.21...v1.4.22](https://github.com/OneBusAway/python-sdk/compare/v1.4.21...v1.4.22) + +### Chores + +* **internal:** change default timeout to an int ([#242](https://github.com/OneBusAway/python-sdk/issues/242)) ([7a6abd4](https://github.com/OneBusAway/python-sdk/commit/7a6abd4930719c6451e3701f745f028999bf00b0)) + +## 1.4.21 (2025-01-24) + +Full Changelog: [v1.4.20...v1.4.21](https://github.com/OneBusAway/python-sdk/compare/v1.4.20...v1.4.21) + +### Chores + +* **internal:** minor formatting changes ([#239](https://github.com/OneBusAway/python-sdk/issues/239)) ([c4753be](https://github.com/OneBusAway/python-sdk/commit/c4753be98ddcd9ef1821bf155715366a13de4542)) + +## 1.4.20 (2025-01-23) + +Full Changelog: [v1.4.19...v1.4.20](https://github.com/OneBusAway/python-sdk/compare/v1.4.19...v1.4.20) + +### Chores + +* **internal:** codegen related update ([#236](https://github.com/OneBusAway/python-sdk/issues/236)) ([efffa87](https://github.com/OneBusAway/python-sdk/commit/efffa87da71fe633d206c9816f6ee79ecd564276)) + +## 1.4.19 (2025-01-22) + +Full Changelog: [v1.4.18...v1.4.19](https://github.com/OneBusAway/python-sdk/compare/v1.4.18...v1.4.19) + +### Chores + +* **internal:** codegen related update ([#233](https://github.com/OneBusAway/python-sdk/issues/233)) ([2ee9813](https://github.com/OneBusAway/python-sdk/commit/2ee9813f5d3b4a4f3036f2cce685f1e2c4adaa71)) + +## 1.4.18 (2025-01-21) + +Full Changelog: [v1.4.17...v1.4.18](https://github.com/OneBusAway/python-sdk/compare/v1.4.17...v1.4.18) + +### Documentation + +* **raw responses:** fix duplicate `the` ([#229](https://github.com/OneBusAway/python-sdk/issues/229)) ([3eb85fa](https://github.com/OneBusAway/python-sdk/commit/3eb85fa39efc772ba9175c5a281e19c2e22b62d4)) + +## 1.4.17 (2025-01-17) + +Full Changelog: [v1.4.16...v1.4.17](https://github.com/OneBusAway/python-sdk/compare/v1.4.16...v1.4.17) + +### Chores + +* **internal:** codegen related update ([#226](https://github.com/OneBusAway/python-sdk/issues/226)) ([69f99d3](https://github.com/OneBusAway/python-sdk/commit/69f99d32d95b5256685d68874c73b2647cc8e900)) + +## 1.4.16 (2025-01-10) + +Full Changelog: [v1.4.15...v1.4.16](https://github.com/OneBusAway/python-sdk/compare/v1.4.15...v1.4.16) + +### Bug Fixes + +* correctly handle deserialising `cls` fields ([#223](https://github.com/OneBusAway/python-sdk/issues/223)) ([33d5ab7](https://github.com/OneBusAway/python-sdk/commit/33d5ab7fc1366619e6db0a2ab30390ee3249bf52)) + +## 1.4.15 (2025-01-09) + +Full Changelog: [v1.4.14...v1.4.15](https://github.com/OneBusAway/python-sdk/compare/v1.4.14...v1.4.15) + +### Chores + +* **internal:** codegen related update ([#220](https://github.com/OneBusAway/python-sdk/issues/220)) ([d78e83a](https://github.com/OneBusAway/python-sdk/commit/d78e83aea265d25d3a37e2fc0c1ec3aa8ea3c9f5)) + +## 1.4.14 (2025-01-09) + +Full Changelog: [v1.4.13...v1.4.14](https://github.com/OneBusAway/python-sdk/compare/v1.4.13...v1.4.14) + +### Documentation + +* fix typos ([#218](https://github.com/OneBusAway/python-sdk/issues/218)) ([b66f7dd](https://github.com/OneBusAway/python-sdk/commit/b66f7dd5482920dfb986bd3e6c09c62b3fdfafb3)) + +## 1.4.13 (2025-01-08) + +Full Changelog: [v1.4.12...v1.4.13](https://github.com/OneBusAway/python-sdk/compare/v1.4.12...v1.4.13) + +### Chores + +* **internal:** codegen related update ([#215](https://github.com/OneBusAway/python-sdk/issues/215)) ([f3ecad9](https://github.com/OneBusAway/python-sdk/commit/f3ecad9cbd05c928200b9ee53c2a3a0615f99822)) + +## 1.4.12 (2025-01-07) + +Full Changelog: [v1.4.11...v1.4.12](https://github.com/OneBusAway/python-sdk/compare/v1.4.11...v1.4.12) + +### Chores + +* add missing isclass check ([#212](https://github.com/OneBusAway/python-sdk/issues/212)) ([989d621](https://github.com/OneBusAway/python-sdk/commit/989d6218de97f5515ec741b5cc424cb8d7901362)) + +## 1.4.11 (2025-01-02) + +Full Changelog: [v1.4.10...v1.4.11](https://github.com/OneBusAway/python-sdk/compare/v1.4.10...v1.4.11) + +### Chores + +* **internal:** codegen related update ([#209](https://github.com/OneBusAway/python-sdk/issues/209)) ([922f218](https://github.com/OneBusAway/python-sdk/commit/922f218506637cff878bdd44b64a886323371ea2)) + +## 1.4.10 (2024-12-21) + +Full Changelog: [v1.4.9...v1.4.10](https://github.com/OneBusAway/python-sdk/compare/v1.4.9...v1.4.10) + +### Chores + +* **internal:** fix some typos ([#205](https://github.com/OneBusAway/python-sdk/issues/205)) ([c539ec3](https://github.com/OneBusAway/python-sdk/commit/c539ec33bb73497f8f16ec7b5868f761cd0c3cb6)) + +## 1.4.9 (2024-12-17) + +Full Changelog: [v1.4.8...v1.4.9](https://github.com/OneBusAway/python-sdk/compare/v1.4.8...v1.4.9) + +### Chores + +* **internal:** codegen related update ([#201](https://github.com/OneBusAway/python-sdk/issues/201)) ([a8bf61b](https://github.com/OneBusAway/python-sdk/commit/a8bf61bad113e964dd82013827ae3e3968a41527)) + +## 1.4.8 (2024-12-17) + +Full Changelog: [v1.4.7...v1.4.8](https://github.com/OneBusAway/python-sdk/compare/v1.4.7...v1.4.8) + +### Chores + +* **internal:** codegen related update ([#198](https://github.com/OneBusAway/python-sdk/issues/198)) ([2210e02](https://github.com/OneBusAway/python-sdk/commit/2210e02b9e700135b282629389534a2ec064ef0d)) + +## 1.4.7 (2024-12-17) + +Full Changelog: [v1.4.6...v1.4.7](https://github.com/OneBusAway/python-sdk/compare/v1.4.6...v1.4.7) + +### Chores + +* **internal:** codegen related update ([#197](https://github.com/OneBusAway/python-sdk/issues/197)) ([12f69d6](https://github.com/OneBusAway/python-sdk/commit/12f69d6a786450bdb2c8588775ba05709b61de79)) + +## 1.4.6 (2024-12-14) + +Full Changelog: [v1.4.5...v1.4.6](https://github.com/OneBusAway/python-sdk/compare/v1.4.5...v1.4.6) + +### Chores + +* **internal:** codegen related update ([#194](https://github.com/OneBusAway/python-sdk/issues/194)) ([7e10c0d](https://github.com/OneBusAway/python-sdk/commit/7e10c0d81a717032bc6f10ff61ea49c6057f5610)) + +## 1.4.5 (2024-12-13) + +Full Changelog: [v1.4.4...v1.4.5](https://github.com/OneBusAway/python-sdk/compare/v1.4.4...v1.4.5) + +### Chores + +* **internal:** codegen related update ([#191](https://github.com/OneBusAway/python-sdk/issues/191)) ([7bd6b7d](https://github.com/OneBusAway/python-sdk/commit/7bd6b7dd25b58fcc1e995086a380975f25535065)) + +## 1.4.4 (2024-12-10) + +Full Changelog: [v1.4.3...v1.4.4](https://github.com/OneBusAway/python-sdk/compare/v1.4.3...v1.4.4) + +### Chores + +* **internal:** codegen related update ([#188](https://github.com/OneBusAway/python-sdk/issues/188)) ([6a5ace1](https://github.com/OneBusAway/python-sdk/commit/6a5ace19835fb7d3ebe9dc8c29883b5002b9967a)) + +## 1.4.3 (2024-12-10) + +Full Changelog: [v1.4.2...v1.4.3](https://github.com/OneBusAway/python-sdk/compare/v1.4.2...v1.4.3) + +### Chores + +* **internal:** bump pydantic dependency ([#185](https://github.com/OneBusAway/python-sdk/issues/185)) ([592e261](https://github.com/OneBusAway/python-sdk/commit/592e2619ae0d6b486c14c25812f7e0c5402fe068)) + +## 1.4.2 (2024-12-04) + +Full Changelog: [v1.4.1...v1.4.2](https://github.com/OneBusAway/python-sdk/compare/v1.4.1...v1.4.2) + +### Chores + +* make the `Omit` type public ([#182](https://github.com/OneBusAway/python-sdk/issues/182)) ([cd0f27e](https://github.com/OneBusAway/python-sdk/commit/cd0f27e796d255cf20b23b6418c200faec5b35d0)) + +## 1.4.1 (2024-12-03) + +Full Changelog: [v1.4.0...v1.4.1](https://github.com/OneBusAway/python-sdk/compare/v1.4.0...v1.4.1) + +### Chores + +* **internal:** codegen related update ([#179](https://github.com/OneBusAway/python-sdk/issues/179)) ([113387e](https://github.com/OneBusAway/python-sdk/commit/113387ece3ec8ba6cfa09f9097c083e8858a7a2c)) + +## 1.4.0 (2024-11-29) + +Full Changelog: [v1.3.0...v1.4.0](https://github.com/OneBusAway/python-sdk/compare/v1.3.0...v1.4.0) + +### Features + +* **api:** api update ([#176](https://github.com/OneBusAway/python-sdk/issues/176)) ([b66eab7](https://github.com/OneBusAway/python-sdk/commit/b66eab729a4ace0919b928169f793e5ffdb62a47)) + +## 1.3.0 (2024-11-29) + +Full Changelog: [v1.2.13...v1.3.0](https://github.com/OneBusAway/python-sdk/compare/v1.2.13...v1.3.0) + +### Features + +* **api:** api update ([#173](https://github.com/OneBusAway/python-sdk/issues/173)) ([91ac1e7](https://github.com/OneBusAway/python-sdk/commit/91ac1e776ed5f17d523bb61ec78d9aa9f037c5c4)) + +## 1.2.13 (2024-11-28) + +Full Changelog: [v1.2.12...v1.2.13](https://github.com/OneBusAway/python-sdk/compare/v1.2.12...v1.2.13) + +### Bug Fixes + +* **client:** compat with new httpx 0.28.0 release ([#170](https://github.com/OneBusAway/python-sdk/issues/170)) ([0409197](https://github.com/OneBusAway/python-sdk/commit/0409197d1a693e5882a4553bb5ce223c12d42966)) + +## 1.2.12 (2024-11-28) + +Full Changelog: [v1.2.11...v1.2.12](https://github.com/OneBusAway/python-sdk/compare/v1.2.11...v1.2.12) + +### Chores + +* **internal:** exclude mypy from running on tests ([#167](https://github.com/OneBusAway/python-sdk/issues/167)) ([e5a89a4](https://github.com/OneBusAway/python-sdk/commit/e5a89a4ded2e8cedee272429a77c6edd921bc55d)) + +## 1.2.11 (2024-11-26) + +Full Changelog: [v1.2.10...v1.2.11](https://github.com/OneBusAway/python-sdk/compare/v1.2.10...v1.2.11) + +### Chores + +* remove now unused `cached-property` dep ([#164](https://github.com/OneBusAway/python-sdk/issues/164)) ([327cdeb](https://github.com/OneBusAway/python-sdk/commit/327cdeb051074bbdb55fe9848c714e46d4c0f22b)) + +## 1.2.10 (2024-11-22) + +Full Changelog: [v1.2.9...v1.2.10](https://github.com/OneBusAway/python-sdk/compare/v1.2.9...v1.2.10) + +### Documentation + +* add info log level to readme ([#161](https://github.com/OneBusAway/python-sdk/issues/161)) ([4b74a14](https://github.com/OneBusAway/python-sdk/commit/4b74a1477b8492d102775395bd688fb2cefd17fa)) + +## 1.2.9 (2024-11-22) + +Full Changelog: [v1.2.8...v1.2.9](https://github.com/OneBusAway/python-sdk/compare/v1.2.8...v1.2.9) + +### Chores + +* **internal:** fix compat model_dump method when warnings are passed ([#158](https://github.com/OneBusAway/python-sdk/issues/158)) ([699d488](https://github.com/OneBusAway/python-sdk/commit/699d4889cf96d12c9fc0c538e228daaab97084be)) + +## 1.2.8 (2024-11-18) + +Full Changelog: [v1.2.7...v1.2.8](https://github.com/OneBusAway/python-sdk/compare/v1.2.7...v1.2.8) + +### Chores + +* rebuild project due to codegen change ([#155](https://github.com/OneBusAway/python-sdk/issues/155)) ([a1206f8](https://github.com/OneBusAway/python-sdk/commit/a1206f8cc69bef31c4932b8160bdb64a1458597d)) + +## 1.2.7 (2024-11-12) + +Full Changelog: [v1.2.6...v1.2.7](https://github.com/OneBusAway/python-sdk/compare/v1.2.6...v1.2.7) + +### Chores + +* rebuild project due to codegen change ([#152](https://github.com/OneBusAway/python-sdk/issues/152)) ([0ccfcf3](https://github.com/OneBusAway/python-sdk/commit/0ccfcf34a65fe87d1b226598585022c305ffc41f)) + +## 1.2.6 (2024-11-12) + +Full Changelog: [v1.2.5...v1.2.6](https://github.com/OneBusAway/python-sdk/compare/v1.2.5...v1.2.6) + +### Chores + +* rebuild project due to codegen change ([#149](https://github.com/OneBusAway/python-sdk/issues/149)) ([a4f5f5c](https://github.com/OneBusAway/python-sdk/commit/a4f5f5c8374fd0882ec00d8fbd79d1ddb8ecf684)) + +## 1.2.5 (2024-11-06) + +Full Changelog: [v1.2.4...v1.2.5](https://github.com/OneBusAway/python-sdk/compare/v1.2.4...v1.2.5) + +### Chores + +* rebuild project due to codegen change ([#144](https://github.com/OneBusAway/python-sdk/issues/144)) ([8a06a1e](https://github.com/OneBusAway/python-sdk/commit/8a06a1eb978f762a36d17dc9ff83fbc8a13493a8)) +* rebuild project due to codegen change ([#146](https://github.com/OneBusAway/python-sdk/issues/146)) ([2dcaaff](https://github.com/OneBusAway/python-sdk/commit/2dcaaffbd9de815c6a1b26091e142c1f94bb5e4e)) + +## 1.2.4 (2024-11-04) + +Full Changelog: [v1.2.3...v1.2.4](https://github.com/OneBusAway/python-sdk/compare/v1.2.3...v1.2.4) + +### Chores + +* rebuild project due to codegen change ([#141](https://github.com/OneBusAway/python-sdk/issues/141)) ([d52c16a](https://github.com/OneBusAway/python-sdk/commit/d52c16af30fdfb61b324c5d83f4052726f2eacd8)) + +## 1.2.3 (2024-11-02) + +Full Changelog: [v1.2.2...v1.2.3](https://github.com/OneBusAway/python-sdk/compare/v1.2.2...v1.2.3) + +### Chores + +* rebuild project due to codegen change ([#138](https://github.com/OneBusAway/python-sdk/issues/138)) ([2b313bd](https://github.com/OneBusAway/python-sdk/commit/2b313bd9f439d8e908a79b5f498e490ce68a06fe)) + +## 1.2.2 (2024-11-01) + +Full Changelog: [v1.2.1...v1.2.2](https://github.com/OneBusAway/python-sdk/compare/v1.2.1...v1.2.2) + +### Chores + +* rebuild project due to codegen change ([#135](https://github.com/OneBusAway/python-sdk/issues/135)) ([34f0283](https://github.com/OneBusAway/python-sdk/commit/34f02834030b10d71061287edcdfe7104f5fccbd)) + +## 1.2.1 (2024-10-28) + +Full Changelog: [v1.2.0...v1.2.1](https://github.com/OneBusAway/python-sdk/compare/v1.2.0...v1.2.1) + +### Chores + +* rebuild project due to codegen change ([#132](https://github.com/OneBusAway/python-sdk/issues/132)) ([39da39b](https://github.com/OneBusAway/python-sdk/commit/39da39bb4d2e0e644a66a24b199f7b4e506c46fd)) + +## 1.2.0 (2024-10-22) + +Full Changelog: [v1.1.6...v1.2.0](https://github.com/OneBusAway/python-sdk/compare/v1.1.6...v1.2.0) + +### Features + +* **api:** api update ([#129](https://github.com/OneBusAway/python-sdk/issues/129)) ([fa000a7](https://github.com/OneBusAway/python-sdk/commit/fa000a7c52befc4916757055e2019174bba553f5)) + +## 1.1.6 (2024-10-07) + +Full Changelog: [v1.1.5...v1.1.6](https://github.com/OneBusAway/python-sdk/compare/v1.1.5...v1.1.6) + +### Chores + +* add repr to PageInfo class ([#126](https://github.com/OneBusAway/python-sdk/issues/126)) ([4904f20](https://github.com/OneBusAway/python-sdk/commit/4904f2016d302d002f08455737f8a76c5784980e)) + +## 1.1.5 (2024-10-07) + +Full Changelog: [v1.1.4...v1.1.5](https://github.com/OneBusAway/python-sdk/compare/v1.1.4...v1.1.5) + +### Bug Fixes + +* **client:** avoid OverflowError with very large retry counts ([#124](https://github.com/OneBusAway/python-sdk/issues/124)) ([5f67626](https://github.com/OneBusAway/python-sdk/commit/5f6762604b6462d1fbac9b72adba324d2e4cc38d)) + +## 1.1.4 (2024-10-07) + +Full Changelog: [v1.1.3...v1.1.4](https://github.com/OneBusAway/python-sdk/compare/v1.1.3...v1.1.4) + +### Chores + +* **internal:** add support for parsing bool response content ([#121](https://github.com/OneBusAway/python-sdk/issues/121)) ([390f17a](https://github.com/OneBusAway/python-sdk/commit/390f17a0a9ca9da54dac53982bfd72fa851191ff)) + +## 1.1.3 (2024-10-02) + +Full Changelog: [v1.1.2...v1.1.3](https://github.com/OneBusAway/python-sdk/compare/v1.1.2...v1.1.3) + +### Chores + +* **internal:** codegen related update ([#116](https://github.com/OneBusAway/python-sdk/issues/116)) ([2f6a75e](https://github.com/OneBusAway/python-sdk/commit/2f6a75ec99dd716f9bdf2a496e8539c523845501)) + +## 1.1.2 (2024-10-02) + +Full Changelog: [v1.1.1...v1.1.2](https://github.com/OneBusAway/python-sdk/compare/v1.1.1...v1.1.2) + +### Chores + +* **internal:** codegen related update ([#115](https://github.com/OneBusAway/python-sdk/issues/115)) ([ca402ef](https://github.com/OneBusAway/python-sdk/commit/ca402ef31d5bdba075c4a2888ab72905e14dc2cc)) + +## 1.1.1 (2024-09-27) + +Full Changelog: [v1.1.0...v1.1.1](https://github.com/OneBusAway/python-sdk/compare/v1.1.0...v1.1.1) + +### Chores + +* **internal:** codegen related update ([#112](https://github.com/OneBusAway/python-sdk/issues/112)) ([646ad67](https://github.com/OneBusAway/python-sdk/commit/646ad67c30929ccb7bc40e6667a93712284c3c30)) + +## 1.1.0 (2024-09-11) + +Full Changelog: [v1.0.0...v1.1.0](https://github.com/OneBusAway/python-sdk/compare/v1.0.0...v1.1.0) + +### Features + +* **api:** OpenAPI spec update via Stainless API ([#110](https://github.com/OneBusAway/python-sdk/issues/110)) ([6ae5ddf](https://github.com/OneBusAway/python-sdk/commit/6ae5ddf666032f4374abf719b02127326c8b344f)) + + +### Chores + +* add docstrings to raw response properties ([#107](https://github.com/OneBusAway/python-sdk/issues/107)) ([0fecff5](https://github.com/OneBusAway/python-sdk/commit/0fecff5be41b8bc6049a49ed56eb80399eccf775)) +* **internal:** codegen related update ([#105](https://github.com/OneBusAway/python-sdk/issues/105)) ([1c46884](https://github.com/OneBusAway/python-sdk/commit/1c46884dffc5766e2c0b03826b341e658ef4b58b)) +* **internal:** codegen related update ([#109](https://github.com/OneBusAway/python-sdk/issues/109)) ([89d809e](https://github.com/OneBusAway/python-sdk/commit/89d809e01baa6cd1b1b98c45e88853584f8ac8a3)) + + +### Documentation + +* **readme:** add section on determining installed version ([#108](https://github.com/OneBusAway/python-sdk/issues/108)) ([01b74e9](https://github.com/OneBusAway/python-sdk/commit/01b74e91f822fedd4407b5de3d2fca1d03aaecbb)) + +## 1.0.0 (2024-08-28) + +Full Changelog: [v0.1.0-alpha.19...v1.0.0](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.19...v1.0.0) + +### Chores + +* release: 1.0.0 ([709a130](https://github.com/OneBusAway/python-sdk/commit/709a1305f5dfd8aed9f95ee69ffd0f192c1b413e)) + +## 0.1.0-alpha.19 (2024-08-24) + +Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.18...v0.1.0-alpha.19) + +### Features + +* **api:** OpenAPI spec update via Stainless API ([#99](https://github.com/OneBusAway/python-sdk/issues/99)) ([e2d03b3](https://github.com/OneBusAway/python-sdk/commit/e2d03b34d459ca5954c40af72c0a8ec13ca19f7e)) + +## 0.1.0-alpha.18 (2024-08-20) + +Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.17...v0.1.0-alpha.18) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#81](https://github.com/OneBusAway/python-sdk/issues/81)) ([71f8ac8](https://github.com/OneBusAway/python-sdk/commit/71f8ac8808bc984dc772435e3dd25fb851a27156)) +- **api:** OpenAPI spec update via Stainless API ([#85](https://github.com/OneBusAway/python-sdk/issues/85)) ([87a9f3f](https://github.com/OneBusAway/python-sdk/commit/87a9f3fe8cecfc7c527ab99cd98fcd93d5a62084)) +- **api:** OpenAPI spec update via Stainless API ([#86](https://github.com/OneBusAway/python-sdk/issues/86)) ([d63524a](https://github.com/OneBusAway/python-sdk/commit/d63524a8e1e7a3ab863e964f2f81069fdd4e5761)) +- **api:** OpenAPI spec update via Stainless API ([#87](https://github.com/OneBusAway/python-sdk/issues/87)) ([d4f2ca0](https://github.com/OneBusAway/python-sdk/commit/d4f2ca03ce7778c127e70a6f7db86e0fb667e157)) +- **api:** OpenAPI spec update via Stainless API ([#88](https://github.com/OneBusAway/python-sdk/issues/88)) ([3543b38](https://github.com/OneBusAway/python-sdk/commit/3543b38cf9e4b8a4762c41f494c8f54a6451d091)) +- **api:** OpenAPI spec update via Stainless API ([#89](https://github.com/OneBusAway/python-sdk/issues/89)) ([b56865a](https://github.com/OneBusAway/python-sdk/commit/b56865a9f16ef4117ac278dec6174a3507b4b1dc)) +- **api:** OpenAPI spec update via Stainless API ([#93](https://github.com/OneBusAway/python-sdk/issues/93)) ([be44ac5](https://github.com/OneBusAway/python-sdk/commit/be44ac517f04b963fc27b68d07f94b7da9fb7380)) +- **api:** OpenAPI spec update via Stainless API ([#94](https://github.com/OneBusAway/python-sdk/issues/94)) ([2ebd77c](https://github.com/OneBusAway/python-sdk/commit/2ebd77c7d359fd48b7ed8104223db7abc0157de1)) +- **api:** OpenAPI spec update via Stainless API ([#95](https://github.com/OneBusAway/python-sdk/issues/95)) ([54d58cf](https://github.com/OneBusAway/python-sdk/commit/54d58cf81718aee18e852dda9ad260dfa9392404)) +- **api:** OpenAPI spec update via Stainless API ([#96](https://github.com/OneBusAway/python-sdk/issues/96)) ([7cc92e8](https://github.com/OneBusAway/python-sdk/commit/7cc92e8d051f011599844cd9d3851521a94cc49b)) +- **api:** OpenAPI spec update via Stainless API ([#97](https://github.com/OneBusAway/python-sdk/issues/97)) ([c3ca610](https://github.com/OneBusAway/python-sdk/commit/c3ca610df71affbe1381fcfa5a85f22614098beb)) + +### Chores + +- **ci:** also run pydantic v1 tests ([#92](https://github.com/OneBusAway/python-sdk/issues/92)) ([19a0334](https://github.com/OneBusAway/python-sdk/commit/19a03345944154c4db06c96304e2e9b5bb2cbc57)) +- **client:** fix parsing union responses when non-json is returned ([#91](https://github.com/OneBusAway/python-sdk/issues/91)) ([38860c1](https://github.com/OneBusAway/python-sdk/commit/38860c19de2e79ab7ca5054c2ec9357a1fd3690b)) +- **internal:** codegen related update ([#83](https://github.com/OneBusAway/python-sdk/issues/83)) ([bb39847](https://github.com/OneBusAway/python-sdk/commit/bb398472831ec5623b587568acfd99ecd44be886)) +- **internal:** use different 32bit detection method ([#84](https://github.com/OneBusAway/python-sdk/issues/84)) ([8dbeb49](https://github.com/OneBusAway/python-sdk/commit/8dbeb49a6f799aaef5214d77cfa46ae0d45fd635)) + +## 0.1.0-alpha.17 (2024-08-12) + +Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.16...v0.1.0-alpha.17) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#77](https://github.com/OneBusAway/python-sdk/issues/77)) ([70ce97c](https://github.com/OneBusAway/python-sdk/commit/70ce97c2c90dcfa6635fd36f83a7fb71a321ca69)) +- **api:** update via SDK Studio ([#79](https://github.com/OneBusAway/python-sdk/issues/79)) ([b31341a](https://github.com/OneBusAway/python-sdk/commit/b31341a157b8b9aa554ad161afcbc581a041c3fb)) + +## 0.1.0-alpha.16 (2024-08-10) + +Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.15...v0.1.0-alpha.16) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#71](https://github.com/OneBusAway/python-sdk/issues/71)) ([876b77c](https://github.com/OneBusAway/python-sdk/commit/876b77c3addf2460c3116738f7b4bc784f67350d)) +- **api:** OpenAPI spec update via Stainless API ([#75](https://github.com/OneBusAway/python-sdk/issues/75)) ([6af5d8a](https://github.com/OneBusAway/python-sdk/commit/6af5d8a93d7f516c9bb7a20e20d063944d0435e9)) + +### Chores + +- **ci:** bump prism mock server version ([#73](https://github.com/OneBusAway/python-sdk/issues/73)) ([e303bb6](https://github.com/OneBusAway/python-sdk/commit/e303bb64a39d45801d72141e13e00bac40771f71)) +- **internal:** ensure package is importable in lint cmd ([#74](https://github.com/OneBusAway/python-sdk/issues/74)) ([b4db5b9](https://github.com/OneBusAway/python-sdk/commit/b4db5b955b4880c1eea3bba00749bb9f4a78416b)) + +## 0.1.0-alpha.15 (2024-08-08) + +Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.14...v0.1.0-alpha.15) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#68](https://github.com/OneBusAway/python-sdk/issues/68)) ([6367613](https://github.com/OneBusAway/python-sdk/commit/63676136b6d0b0f01738cfc778763b6a79775d09)) + +## 0.1.0-alpha.14 (2024-08-08) + +Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.13...v0.1.0-alpha.14) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#65](https://github.com/OneBusAway/python-sdk/issues/65)) ([d93e82a](https://github.com/OneBusAway/python-sdk/commit/d93e82a4b6410768f1d5f9cd08ee83157eb405ce)) + +## 0.1.0-alpha.13 (2024-08-08) + +Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.12...v0.1.0-alpha.13) + +### Chores + +- **internal:** version bump ([#61](https://github.com/OneBusAway/python-sdk/issues/61)) ([5d7fe06](https://github.com/OneBusAway/python-sdk/commit/5d7fe060b6158f719e388feba0bc9f7dcb0fe288)) + +## 0.1.0-alpha.12 (2024-08-08) + +Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.11...v0.1.0-alpha.12) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#48](https://github.com/OneBusAway/python-sdk/issues/48)) ([a5c305c](https://github.com/OneBusAway/python-sdk/commit/a5c305c7a5614588dd3fb14607dd2489aab8b3c6)) +- **api:** OpenAPI spec update via Stainless API ([#55](https://github.com/OneBusAway/python-sdk/issues/55)) ([9adae4d](https://github.com/OneBusAway/python-sdk/commit/9adae4de96b8ea11ff8e2d7c251183580f86cf8a)) +- **api:** OpenAPI spec update via Stainless API ([#56](https://github.com/OneBusAway/python-sdk/issues/56)) ([1882cdc](https://github.com/OneBusAway/python-sdk/commit/1882cdc8a4da9705c853ed6f5d5e8d73a4b9276c)) +- **api:** OpenAPI spec update via Stainless API ([#57](https://github.com/OneBusAway/python-sdk/issues/57)) ([5c8a7ee](https://github.com/OneBusAway/python-sdk/commit/5c8a7ee00dcd4ab6a3a9b6800fe10f7447d79c61)) +- **api:** OpenAPI spec update via Stainless API ([#58](https://github.com/OneBusAway/python-sdk/issues/58)) ([29edb45](https://github.com/OneBusAway/python-sdk/commit/29edb45798ad111a1a4f6ce5bbbbadfe3e9099d8)) +- **client:** add `retry_count` to raw response class ([#51](https://github.com/OneBusAway/python-sdk/issues/51)) ([97156e5](https://github.com/OneBusAway/python-sdk/commit/97156e5dff5d743d04ecc0dbe73ec7c690dfb4fe)) + +### Chores + +- **internal:** bump pyright ([#50](https://github.com/OneBusAway/python-sdk/issues/50)) ([6d1fdf7](https://github.com/OneBusAway/python-sdk/commit/6d1fdf78cf6d63079bf037b88ac0540e619f9ae7)) +- **internal:** bump ruff version ([#53](https://github.com/OneBusAway/python-sdk/issues/53)) ([c8cbc6a](https://github.com/OneBusAway/python-sdk/commit/c8cbc6a1e48e42b15e42ebf40cdfd2f8fc0bc7ab)) +- **internal:** remove deprecated ruff config ([#59](https://github.com/OneBusAway/python-sdk/issues/59)) ([a6413af](https://github.com/OneBusAway/python-sdk/commit/a6413af808c1ea04d05a46fa0a5d54ec888999bf)) +- **internal:** test updates ([#52](https://github.com/OneBusAway/python-sdk/issues/52)) ([f3b7cb0](https://github.com/OneBusAway/python-sdk/commit/f3b7cb00c4302b0ac1b8497835f2db0d3f9e7949)) +- **internal:** update pydantic compat helper function ([#54](https://github.com/OneBusAway/python-sdk/issues/54)) ([1f501f0](https://github.com/OneBusAway/python-sdk/commit/1f501f0592c92e86c4c027f652a49ff11a068715)) + +## 0.1.0-alpha.11 (2024-08-01) + +Full Changelog: [v0.1.0-alpha.10...v0.1.0-alpha.11](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.10...v0.1.0-alpha.11) + +### Features + +- **api:** OpenAPI spec update via Stainless API ([#43](https://github.com/OneBusAway/python-sdk/issues/43)) ([d80d795](https://github.com/OneBusAway/python-sdk/commit/d80d795cf0dfce32a27986630065fcf3f93a0eaa)) +- **api:** OpenAPI spec update via Stainless API ([#44](https://github.com/OneBusAway/python-sdk/issues/44)) ([c79102d](https://github.com/OneBusAway/python-sdk/commit/c79102d2c079e23c77dd4583485b967cf8eaddc1)) +- **api:** OpenAPI spec update via Stainless API ([#45](https://github.com/OneBusAway/python-sdk/issues/45)) ([138c1c3](https://github.com/OneBusAway/python-sdk/commit/138c1c35902a68e251796e4713fcfeb609fb2592)) +- chore: Refactor code by removing unnecessary blank lines ([9dfffd8](https://github.com/OneBusAway/python-sdk/commit/9dfffd881f9bbe738a2af883ef1dae8db68c7eb7)) +- various codegen changes ([1cba822](https://github.com/OneBusAway/python-sdk/commit/1cba822390c0a6e2c2d8f3f9e101215a1abc22cc)) + ## 0.1.0-alpha.10 (2024-07-31) Full Changelog: [v0.1.0-alpha.9...v0.1.0-alpha.10](https://github.com/OneBusAway/python-sdk/compare/v0.1.0-alpha.9...v0.1.0-alpha.10) ### Features -* **api:** OpenAPI spec update via Stainless API ([#37](https://github.com/OneBusAway/python-sdk/issues/37)) ([cc7611d](https://github.com/OneBusAway/python-sdk/commit/cc7611d6984efe9cce70b1388c8bdfd14ed814d3)) -* **api:** OpenAPI spec update via Stainless API ([#39](https://github.com/OneBusAway/python-sdk/issues/39)) ([45dc9fe](https://github.com/OneBusAway/python-sdk/commit/45dc9fe6b89ab5224d6f60d97d10ab85326e08fd)) +- **api:** OpenAPI spec update via Stainless API ([#37](https://github.com/OneBusAway/python-sdk/issues/37)) ([cc7611d](https://github.com/OneBusAway/python-sdk/commit/cc7611d6984efe9cce70b1388c8bdfd14ed814d3)) +- **api:** OpenAPI spec update via Stainless API ([#39](https://github.com/OneBusAway/python-sdk/issues/39)) ([45dc9fe](https://github.com/OneBusAway/python-sdk/commit/45dc9fe6b89ab5224d6f60d97d10ab85326e08fd)) ## 0.1.0-alpha.9 (2024-07-31) @@ -15,7 +1176,7 @@ Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#34](https://github.com/OneBusAway/python-sdk/issues/34)) ([d60f5d7](https://github.com/OneBusAway/python-sdk/commit/d60f5d72bed809667875d4548bc4909d5125a6d5)) +- **api:** OpenAPI spec update via Stainless API ([#34](https://github.com/OneBusAway/python-sdk/issues/34)) ([d60f5d7](https://github.com/OneBusAway/python-sdk/commit/d60f5d72bed809667875d4548bc4909d5125a6d5)) ## 0.1.0-alpha.8 (2024-07-30) @@ -23,7 +1184,7 @@ Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#31](https://github.com/OneBusAway/python-sdk/issues/31)) ([891cdf5](https://github.com/OneBusAway/python-sdk/commit/891cdf59961594e7bfb21fc25e8615b3c536dc8e)) +- **api:** OpenAPI spec update via Stainless API ([#31](https://github.com/OneBusAway/python-sdk/issues/31)) ([891cdf5](https://github.com/OneBusAway/python-sdk/commit/891cdf59961594e7bfb21fc25e8615b3c536dc8e)) ## 0.1.0-alpha.7 (2024-07-29) @@ -31,7 +1192,7 @@ Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/OneBusAway/ ### Chores -* **internal:** add type construction helper ([#27](https://github.com/OneBusAway/python-sdk/issues/27)) ([ceb8e94](https://github.com/OneBusAway/python-sdk/commit/ceb8e94522516242dc400c281133c053ddcc7961)) +- **internal:** add type construction helper ([#27](https://github.com/OneBusAway/python-sdk/issues/27)) ([ceb8e94](https://github.com/OneBusAway/python-sdk/commit/ceb8e94522516242dc400c281133c053ddcc7961)) ## 0.1.0-alpha.6 (2024-07-29) @@ -39,13 +1200,12 @@ Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#22](https://github.com/OneBusAway/python-sdk/issues/22)) ([54e9f2a](https://github.com/OneBusAway/python-sdk/commit/54e9f2aefaf7a88392bf23ec3301efd2191013a7)) -* **api:** OpenAPI spec update via Stainless API ([#25](https://github.com/OneBusAway/python-sdk/issues/25)) ([64e3e43](https://github.com/OneBusAway/python-sdk/commit/64e3e4396b25d8d80ddee38ec99cda8409b0f597)) - +- **api:** OpenAPI spec update via Stainless API ([#22](https://github.com/OneBusAway/python-sdk/issues/22)) ([54e9f2a](https://github.com/OneBusAway/python-sdk/commit/54e9f2aefaf7a88392bf23ec3301efd2191013a7)) +- **api:** OpenAPI spec update via Stainless API ([#25](https://github.com/OneBusAway/python-sdk/issues/25)) ([64e3e43](https://github.com/OneBusAway/python-sdk/commit/64e3e4396b25d8d80ddee38ec99cda8409b0f597)) ### Chores -* **internal:** add type construction helper ([#24](https://github.com/OneBusAway/python-sdk/issues/24)) ([92184d0](https://github.com/OneBusAway/python-sdk/commit/92184d0e99d1ed3b9ccc13adf5aaa7bc6ddb334b)) +- **internal:** add type construction helper ([#24](https://github.com/OneBusAway/python-sdk/issues/24)) ([92184d0](https://github.com/OneBusAway/python-sdk/commit/92184d0e99d1ed3b9ccc13adf5aaa7bc6ddb334b)) ## 0.1.0-alpha.5 (2024-07-28) @@ -53,7 +1213,7 @@ Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#18](https://github.com/OneBusAway/python-sdk/issues/18)) ([e209b47](https://github.com/OneBusAway/python-sdk/commit/e209b47d717f9ad08a688587b1bae46cb71fab54)) +- **api:** OpenAPI spec update via Stainless API ([#18](https://github.com/OneBusAway/python-sdk/issues/18)) ([e209b47](https://github.com/OneBusAway/python-sdk/commit/e209b47d717f9ad08a688587b1bae46cb71fab54)) ## 0.1.0-alpha.4 (2024-07-27) @@ -61,7 +1221,7 @@ Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#15](https://github.com/OneBusAway/python-sdk/issues/15)) ([171d1b7](https://github.com/OneBusAway/python-sdk/commit/171d1b79ca2cc120d315566f3f861c96a05d8a3b)) +- **api:** OpenAPI spec update via Stainless API ([#15](https://github.com/OneBusAway/python-sdk/issues/15)) ([171d1b7](https://github.com/OneBusAway/python-sdk/commit/171d1b79ca2cc120d315566f3f861c96a05d8a3b)) ## 0.1.0-alpha.3 (2024-07-27) @@ -69,13 +1229,12 @@ Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#10](https://github.com/OneBusAway/python-sdk/issues/10)) ([72b1a9c](https://github.com/OneBusAway/python-sdk/commit/72b1a9cead47f18491e1119a836088e20bb0d5f4)) - +- **api:** OpenAPI spec update via Stainless API ([#10](https://github.com/OneBusAway/python-sdk/issues/10)) ([72b1a9c](https://github.com/OneBusAway/python-sdk/commit/72b1a9cead47f18491e1119a836088e20bb0d5f4)) ### Chores -* **internal:** refactor release doctor script ([#12](https://github.com/OneBusAway/python-sdk/issues/12)) ([ee1f10f](https://github.com/OneBusAway/python-sdk/commit/ee1f10fc42fbda4a4df01d58636d5b6c8209fc70)) -* **tests:** update prism version ([#13](https://github.com/OneBusAway/python-sdk/issues/13)) ([4298ff4](https://github.com/OneBusAway/python-sdk/commit/4298ff401d952e2ed17674b59001bb5d1146ae6d)) +- **internal:** refactor release doctor script ([#12](https://github.com/OneBusAway/python-sdk/issues/12)) ([ee1f10f](https://github.com/OneBusAway/python-sdk/commit/ee1f10fc42fbda4a4df01d58636d5b6c8209fc70)) +- **tests:** update prism version ([#13](https://github.com/OneBusAway/python-sdk/issues/13)) ([4298ff4](https://github.com/OneBusAway/python-sdk/commit/4298ff401d952e2ed17674b59001bb5d1146ae6d)) ## 0.1.0-alpha.2 (2024-07-22) @@ -83,12 +1242,12 @@ Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/OneBusAway/ ### Features -* **api:** OpenAPI spec update via Stainless API ([#4](https://github.com/OneBusAway/python-sdk/issues/4)) ([fc1679d](https://github.com/OneBusAway/python-sdk/commit/fc1679d6086747ac91152587e272d6b2b32a97fd)) -* **api:** OpenAPI spec update via Stainless API ([#6](https://github.com/OneBusAway/python-sdk/issues/6)) ([b6a54cf](https://github.com/OneBusAway/python-sdk/commit/b6a54cfdac1ecfb398e20a4efa90294bde711490)) -* **api:** OpenAPI spec update via Stainless API ([#7](https://github.com/OneBusAway/python-sdk/issues/7)) ([448c0e3](https://github.com/OneBusAway/python-sdk/commit/448c0e3847375dcdb86f9cda1e8206f64910e6e9)) -* **api:** OpenAPI spec update via Stainless API ([#8](https://github.com/OneBusAway/python-sdk/issues/8)) ([361ab73](https://github.com/OneBusAway/python-sdk/commit/361ab733efaef27d9553cabde2844ffb95296979)) -* refactor: Remove print statement from main_sync function ([b72a7a1](https://github.com/OneBusAway/python-sdk/commit/b72a7a16aaf67c5d265cb995181e1abea6d7fed2)) -* refactor: Remove print statement from main_sync function ([dcd9e3b](https://github.com/OneBusAway/python-sdk/commit/dcd9e3b609078c5fcbb9682ae4f92849bc7291cf)) +- **api:** OpenAPI spec update via Stainless API ([#4](https://github.com/OneBusAway/python-sdk/issues/4)) ([fc1679d](https://github.com/OneBusAway/python-sdk/commit/fc1679d6086747ac91152587e272d6b2b32a97fd)) +- **api:** OpenAPI spec update via Stainless API ([#6](https://github.com/OneBusAway/python-sdk/issues/6)) ([b6a54cf](https://github.com/OneBusAway/python-sdk/commit/b6a54cfdac1ecfb398e20a4efa90294bde711490)) +- **api:** OpenAPI spec update via Stainless API ([#7](https://github.com/OneBusAway/python-sdk/issues/7)) ([448c0e3](https://github.com/OneBusAway/python-sdk/commit/448c0e3847375dcdb86f9cda1e8206f64910e6e9)) +- **api:** OpenAPI spec update via Stainless API ([#8](https://github.com/OneBusAway/python-sdk/issues/8)) ([361ab73](https://github.com/OneBusAway/python-sdk/commit/361ab733efaef27d9553cabde2844ffb95296979)) +- refactor: Remove print statement from main_sync function ([b72a7a1](https://github.com/OneBusAway/python-sdk/commit/b72a7a16aaf67c5d265cb995181e1abea6d7fed2)) +- refactor: Remove print statement from main_sync function ([dcd9e3b](https://github.com/OneBusAway/python-sdk/commit/dcd9e3b609078c5fcbb9682ae4f92849bc7291cf)) ## 0.1.0-alpha.1 (2024-07-14) @@ -96,102 +1255,100 @@ Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/OneBusAway/ ### Features -* **api:** update trip resource endpoint to use trip_id parameter ([505ac28](https://github.com/OneBusAway/python-sdk/commit/505ac28c233cdc34210863decd6bce6388d299b3)) -* **api:** update via SDK Studio ([2fc0ce6](https://github.com/OneBusAway/python-sdk/commit/2fc0ce66e2d210b9d39a754dc412a1f78dde2425)) -* **api:** update via SDK Studio ([58b87fc](https://github.com/OneBusAway/python-sdk/commit/58b87fc149b3a748b56a742b1312df4b7a288dc8)) -* **api:** update via SDK Studio ([6fce548](https://github.com/OneBusAway/python-sdk/commit/6fce548afe8bd6c6cc7dfbee8cf2ae7eab4b17b6)) -* **api:** update via SDK Studio ([7586440](https://github.com/OneBusAway/python-sdk/commit/75864402b21644233224847f316a3a4ed226eaa0)) -* **api:** update via SDK Studio ([7a43139](https://github.com/OneBusAway/python-sdk/commit/7a431396cec7698593864f004ae7ae26bc48f407)) -* **api:** update via SDK Studio ([9d44ad8](https://github.com/OneBusAway/python-sdk/commit/9d44ad8bdeeed5dd3565e7382f510a7c7b6c3425)) -* **api:** update via SDK Studio ([92f3ad8](https://github.com/OneBusAway/python-sdk/commit/92f3ad86c78f8fcacb9db900bfcc6c5e1430d6d4)) -* **api:** update via SDK Studio ([6155c59](https://github.com/OneBusAway/python-sdk/commit/6155c591599e910f3c1a6c27fa410f5f95d0efc1)) -* **api:** update via SDK Studio ([c581bdb](https://github.com/OneBusAway/python-sdk/commit/c581bdb1782dba7b12e491968a2bd28aa01da6d3)) -* **api:** update via SDK Studio ([fdb908c](https://github.com/OneBusAway/python-sdk/commit/fdb908c5d0ebf12f331be0c10e7d714ece909296)) -* **api:** update via SDK Studio ([f1b0a5f](https://github.com/OneBusAway/python-sdk/commit/f1b0a5fb344842bda01724c0adb7043f8d83ba3e)) -* **api:** update via SDK Studio ([85038fe](https://github.com/OneBusAway/python-sdk/commit/85038fe848673570ccdfd95b58be840c86e306bd)) -* **api:** update via SDK Studio ([7da9927](https://github.com/OneBusAway/python-sdk/commit/7da992794551c82dfd53ec6f0a04ea745e4faf69)) -* **api:** update via SDK Studio ([9521b6e](https://github.com/OneBusAway/python-sdk/commit/9521b6e7baeae134a86abb81a67967782fe8201e)) -* **api:** update via SDK Studio ([b22b033](https://github.com/OneBusAway/python-sdk/commit/b22b0330bf195b5d23bbe56e8654cf74e8aa4b13)) -* **api:** update via SDK Studio ([f8926c3](https://github.com/OneBusAway/python-sdk/commit/f8926c35c8fc29a84ceb87acf027d5442da9a090)) -* **api:** update via SDK Studio ([0854942](https://github.com/OneBusAway/python-sdk/commit/08549428c36d5fa7e5e1db1b4095cd33e4228bff)) -* **api:** update via SDK Studio ([7ddaf77](https://github.com/OneBusAway/python-sdk/commit/7ddaf77a336bbe1f058e80dd24b3cf10832a5c26)) -* **api:** update via SDK Studio ([0809c44](https://github.com/OneBusAway/python-sdk/commit/0809c449d74a685fb8f3320c397e2ad9602ead5f)) -* **api:** update via SDK Studio ([84da952](https://github.com/OneBusAway/python-sdk/commit/84da952b7e17b6ffe6044b562bb5a8b699e33971)) -* **api:** update via SDK Studio ([db3e659](https://github.com/OneBusAway/python-sdk/commit/db3e659d827c0f782658a9ba5ea66fb1cacd057d)) -* **api:** update via SDK Studio ([3beb328](https://github.com/OneBusAway/python-sdk/commit/3beb328f733f04252725ea46b2b67588a87aa0c4)) -* **api:** update via SDK Studio ([1decfcf](https://github.com/OneBusAway/python-sdk/commit/1decfcf8e8b62d137c205bb68103130954d425e4)) -* **api:** update via SDK Studio ([0ad9c15](https://github.com/OneBusAway/python-sdk/commit/0ad9c159c08945fa8fde230d133934c319af8a21)) -* **api:** update via SDK Studio ([5ce8b85](https://github.com/OneBusAway/python-sdk/commit/5ce8b8522d5dff1435a433699d0eaa21177ed147)) -* **api:** update via SDK Studio ([1b32372](https://github.com/OneBusAway/python-sdk/commit/1b32372e013eaf55acd3fa9fbeca12e9178a9063)) -* **api:** update via SDK Studio ([ba50d73](https://github.com/OneBusAway/python-sdk/commit/ba50d73823485025802164f0a90bcda5d456f488)) -* **api:** update via SDK Studio ([295f030](https://github.com/OneBusAway/python-sdk/commit/295f030e4584f83065c340fa907ebe18c1cca1d7)) -* **api:** update via SDK Studio ([cfdc365](https://github.com/OneBusAway/python-sdk/commit/cfdc365c5711df9aea710ab40f394097be87283c)) -* **api:** update via SDK Studio ([21115c3](https://github.com/OneBusAway/python-sdk/commit/21115c3b2134638118425bf9ec4d682403519c37)) -* **api:** update via SDK Studio ([a80244d](https://github.com/OneBusAway/python-sdk/commit/a80244d027a606fdaf9df9049bb0941a2b29d1ff)) -* **api:** update via SDK Studio ([f2a3418](https://github.com/OneBusAway/python-sdk/commit/f2a34180fb1bbd73adecae83503a4417cc2dd013)) -* **api:** update via SDK Studio ([80a7f9e](https://github.com/OneBusAway/python-sdk/commit/80a7f9ede0b1ff0b4c2de938166a02136b0da2fe)) -* **api:** update via SDK Studio ([b0bab66](https://github.com/OneBusAway/python-sdk/commit/b0bab660fe8a5ae248b554be13eea215957e5f44)) -* **api:** update via SDK Studio ([fc88be6](https://github.com/OneBusAway/python-sdk/commit/fc88be647860cb3bf94d9799ed5fcd607dc2fc50)) -* **api:** update via SDK Studio ([2d32178](https://github.com/OneBusAway/python-sdk/commit/2d321788f506d3ec776ed10a3dafe315ede012a2)) -* **api:** update via SDK Studio ([4094e8a](https://github.com/OneBusAway/python-sdk/commit/4094e8acbf4ce995381c8aaf179ec03011d24a51)) -* **api:** update via SDK Studio ([#1](https://github.com/OneBusAway/python-sdk/issues/1)) ([70824e7](https://github.com/OneBusAway/python-sdk/commit/70824e7f4278cba8fe045dd749d07577a21d2887)) -* **Examples:** add agency endpoint ([c5f808a](https://github.com/OneBusAway/python-sdk/commit/c5f808a122fa2cf10651983f2e2070590fcda24e)) -* **examples:** add example for testing api response ([98d1952](https://github.com/OneBusAway/python-sdk/commit/98d1952000347909010e81cd69363e943a4e33c8)) - - -### Chores - -* configure new SDK language ([23b3c89](https://github.com/OneBusAway/python-sdk/commit/23b3c890438d14ae70957aa275e9305dfaf49648)) -* configure new SDK language ([9a9c0c6](https://github.com/OneBusAway/python-sdk/commit/9a9c0c65509d93e2221e41a9f408d326d6e48211)) -* configure new SDK language ([63065e2](https://github.com/OneBusAway/python-sdk/commit/63065e26f68b57b0c74f457c926b38d7dcf600e7)) -* configure new SDK language ([84b1ca6](https://github.com/OneBusAway/python-sdk/commit/84b1ca654f31302265219242964ab5700ef45148)) -* configure new SDK language ([4b14ec2](https://github.com/OneBusAway/python-sdk/commit/4b14ec2cce6a83b529db5525636ffe14a0148916)) -* configure new SDK language ([267eede](https://github.com/OneBusAway/python-sdk/commit/267eeded6e0de5d709d912ee5940b03660b1c806)) -* format agency.py ([7776ddd](https://github.com/OneBusAway/python-sdk/commit/7776ddde7d1468f85d0e238537e87b6098a09e05)) -* update SDK settings ([9bf975b](https://github.com/OneBusAway/python-sdk/commit/9bf975bd37829d53c730424fe4b3956a63d4d997)) -* update SDK settings ([4886cd2](https://github.com/OneBusAway/python-sdk/commit/4886cd2bdf3a1703e907ee3a273a1be00d025ce6)) +- **api:** update trip resource endpoint to use trip_id parameter ([505ac28](https://github.com/OneBusAway/python-sdk/commit/505ac28c233cdc34210863decd6bce6388d299b3)) +- **api:** update via SDK Studio ([2fc0ce6](https://github.com/OneBusAway/python-sdk/commit/2fc0ce66e2d210b9d39a754dc412a1f78dde2425)) +- **api:** update via SDK Studio ([58b87fc](https://github.com/OneBusAway/python-sdk/commit/58b87fc149b3a748b56a742b1312df4b7a288dc8)) +- **api:** update via SDK Studio ([6fce548](https://github.com/OneBusAway/python-sdk/commit/6fce548afe8bd6c6cc7dfbee8cf2ae7eab4b17b6)) +- **api:** update via SDK Studio ([7586440](https://github.com/OneBusAway/python-sdk/commit/75864402b21644233224847f316a3a4ed226eaa0)) +- **api:** update via SDK Studio ([7a43139](https://github.com/OneBusAway/python-sdk/commit/7a431396cec7698593864f004ae7ae26bc48f407)) +- **api:** update via SDK Studio ([9d44ad8](https://github.com/OneBusAway/python-sdk/commit/9d44ad8bdeeed5dd3565e7382f510a7c7b6c3425)) +- **api:** update via SDK Studio ([92f3ad8](https://github.com/OneBusAway/python-sdk/commit/92f3ad86c78f8fcacb9db900bfcc6c5e1430d6d4)) +- **api:** update via SDK Studio ([6155c59](https://github.com/OneBusAway/python-sdk/commit/6155c591599e910f3c1a6c27fa410f5f95d0efc1)) +- **api:** update via SDK Studio ([c581bdb](https://github.com/OneBusAway/python-sdk/commit/c581bdb1782dba7b12e491968a2bd28aa01da6d3)) +- **api:** update via SDK Studio ([fdb908c](https://github.com/OneBusAway/python-sdk/commit/fdb908c5d0ebf12f331be0c10e7d714ece909296)) +- **api:** update via SDK Studio ([f1b0a5f](https://github.com/OneBusAway/python-sdk/commit/f1b0a5fb344842bda01724c0adb7043f8d83ba3e)) +- **api:** update via SDK Studio ([85038fe](https://github.com/OneBusAway/python-sdk/commit/85038fe848673570ccdfd95b58be840c86e306bd)) +- **api:** update via SDK Studio ([7da9927](https://github.com/OneBusAway/python-sdk/commit/7da992794551c82dfd53ec6f0a04ea745e4faf69)) +- **api:** update via SDK Studio ([9521b6e](https://github.com/OneBusAway/python-sdk/commit/9521b6e7baeae134a86abb81a67967782fe8201e)) +- **api:** update via SDK Studio ([b22b033](https://github.com/OneBusAway/python-sdk/commit/b22b0330bf195b5d23bbe56e8654cf74e8aa4b13)) +- **api:** update via SDK Studio ([f8926c3](https://github.com/OneBusAway/python-sdk/commit/f8926c35c8fc29a84ceb87acf027d5442da9a090)) +- **api:** update via SDK Studio ([0854942](https://github.com/OneBusAway/python-sdk/commit/08549428c36d5fa7e5e1db1b4095cd33e4228bff)) +- **api:** update via SDK Studio ([7ddaf77](https://github.com/OneBusAway/python-sdk/commit/7ddaf77a336bbe1f058e80dd24b3cf10832a5c26)) +- **api:** update via SDK Studio ([0809c44](https://github.com/OneBusAway/python-sdk/commit/0809c449d74a685fb8f3320c397e2ad9602ead5f)) +- **api:** update via SDK Studio ([84da952](https://github.com/OneBusAway/python-sdk/commit/84da952b7e17b6ffe6044b562bb5a8b699e33971)) +- **api:** update via SDK Studio ([db3e659](https://github.com/OneBusAway/python-sdk/commit/db3e659d827c0f782658a9ba5ea66fb1cacd057d)) +- **api:** update via SDK Studio ([3beb328](https://github.com/OneBusAway/python-sdk/commit/3beb328f733f04252725ea46b2b67588a87aa0c4)) +- **api:** update via SDK Studio ([1decfcf](https://github.com/OneBusAway/python-sdk/commit/1decfcf8e8b62d137c205bb68103130954d425e4)) +- **api:** update via SDK Studio ([0ad9c15](https://github.com/OneBusAway/python-sdk/commit/0ad9c159c08945fa8fde230d133934c319af8a21)) +- **api:** update via SDK Studio ([5ce8b85](https://github.com/OneBusAway/python-sdk/commit/5ce8b8522d5dff1435a433699d0eaa21177ed147)) +- **api:** update via SDK Studio ([1b32372](https://github.com/OneBusAway/python-sdk/commit/1b32372e013eaf55acd3fa9fbeca12e9178a9063)) +- **api:** update via SDK Studio ([ba50d73](https://github.com/OneBusAway/python-sdk/commit/ba50d73823485025802164f0a90bcda5d456f488)) +- **api:** update via SDK Studio ([295f030](https://github.com/OneBusAway/python-sdk/commit/295f030e4584f83065c340fa907ebe18c1cca1d7)) +- **api:** update via SDK Studio ([cfdc365](https://github.com/OneBusAway/python-sdk/commit/cfdc365c5711df9aea710ab40f394097be87283c)) +- **api:** update via SDK Studio ([21115c3](https://github.com/OneBusAway/python-sdk/commit/21115c3b2134638118425bf9ec4d682403519c37)) +- **api:** update via SDK Studio ([a80244d](https://github.com/OneBusAway/python-sdk/commit/a80244d027a606fdaf9df9049bb0941a2b29d1ff)) +- **api:** update via SDK Studio ([f2a3418](https://github.com/OneBusAway/python-sdk/commit/f2a34180fb1bbd73adecae83503a4417cc2dd013)) +- **api:** update via SDK Studio ([80a7f9e](https://github.com/OneBusAway/python-sdk/commit/80a7f9ede0b1ff0b4c2de938166a02136b0da2fe)) +- **api:** update via SDK Studio ([b0bab66](https://github.com/OneBusAway/python-sdk/commit/b0bab660fe8a5ae248b554be13eea215957e5f44)) +- **api:** update via SDK Studio ([fc88be6](https://github.com/OneBusAway/python-sdk/commit/fc88be647860cb3bf94d9799ed5fcd607dc2fc50)) +- **api:** update via SDK Studio ([2d32178](https://github.com/OneBusAway/python-sdk/commit/2d321788f506d3ec776ed10a3dafe315ede012a2)) +- **api:** update via SDK Studio ([4094e8a](https://github.com/OneBusAway/python-sdk/commit/4094e8acbf4ce995381c8aaf179ec03011d24a51)) +- **api:** update via SDK Studio ([#1](https://github.com/OneBusAway/python-sdk/issues/1)) ([70824e7](https://github.com/OneBusAway/python-sdk/commit/70824e7f4278cba8fe045dd749d07577a21d2887)) +- **Examples:** add agency endpoint ([c5f808a](https://github.com/OneBusAway/python-sdk/commit/c5f808a122fa2cf10651983f2e2070590fcda24e)) +- **examples:** add example for testing api response ([98d1952](https://github.com/OneBusAway/python-sdk/commit/98d1952000347909010e81cd69363e943a4e33c8)) + +### Chores +- configure new SDK language ([23b3c89](https://github.com/OneBusAway/python-sdk/commit/23b3c890438d14ae70957aa275e9305dfaf49648)) +- configure new SDK language ([9a9c0c6](https://github.com/OneBusAway/python-sdk/commit/9a9c0c65509d93e2221e41a9f408d326d6e48211)) +- configure new SDK language ([63065e2](https://github.com/OneBusAway/python-sdk/commit/63065e26f68b57b0c74f457c926b38d7dcf600e7)) +- configure new SDK language ([84b1ca6](https://github.com/OneBusAway/python-sdk/commit/84b1ca654f31302265219242964ab5700ef45148)) +- configure new SDK language ([4b14ec2](https://github.com/OneBusAway/python-sdk/commit/4b14ec2cce6a83b529db5525636ffe14a0148916)) +- configure new SDK language ([267eede](https://github.com/OneBusAway/python-sdk/commit/267eeded6e0de5d709d912ee5940b03660b1c806)) +- format agency.py ([7776ddd](https://github.com/OneBusAway/python-sdk/commit/7776ddde7d1468f85d0e238537e87b6098a09e05)) +- update SDK settings ([9bf975b](https://github.com/OneBusAway/python-sdk/commit/9bf975bd37829d53c730424fe4b3956a63d4d997)) +- update SDK settings ([4886cd2](https://github.com/OneBusAway/python-sdk/commit/4886cd2bdf3a1703e907ee3a273a1be00d025ce6)) ### Refactors -* **Api:** update agency resource endpoint to use agency_id parameter ([3760fc5](https://github.com/OneBusAway/python-sdk/commit/3760fc5d6969be25dc1228af68d927cae7c41863)) -* **Api:** update API endpoint to use stop_id variable ([91625b8](https://github.com/OneBusAway/python-sdk/commit/91625b8671dd561c340e74c1d8a8ecdd8496fd7b)) -* **api:** update arrival and departure resource endpoints to use stop_id parameter ([f267a9c](https://github.com/OneBusAway/python-sdk/commit/f267a9c20647ee6c2e3b5ab0a4d87862372fdb82)) -* **api:** update tests endpoint to use api_key in param ([446cafd](https://github.com/OneBusAway/python-sdk/commit/446cafd532c627fb46c35887e4d2a32c16e5b726)) -* **client:** update auth_headers method to return an empty dictionary ([8133b61](https://github.com/OneBusAway/python-sdk/commit/8133b61a7776964d965c14276dba02d6639149e2)) -* **EndPoint:** update agency API endpoint to use agency_id variable ([6abcaa6](https://github.com/OneBusAway/python-sdk/commit/6abcaa66d066b5fb5fab379d5d26d12f4c63c806)) -* **examples:** remove commented code and unused imports ([ffe2774](https://github.com/OneBusAway/python-sdk/commit/ffe27745b20e593f2770285d122bf53128b374a0)) -* **route:** update route API endpoint to use route_id variable ([9857e55](https://github.com/OneBusAway/python-sdk/commit/9857e558f2f0ad355ca90af74978e23d48ba0948)) -* **route:** update route resource endpoint to use route_id parameter ([643d71d](https://github.com/OneBusAway/python-sdk/commit/643d71da89b6e02275b032e6bd7a78b17066f06b)) -* **test_transform:** fix base64 encoding issue ([fefa1fb](https://github.com/OneBusAway/python-sdk/commit/fefa1fb8d02dcc8c9b536f11cd3388b3ef3d8055)) -* **test_transform:** fix base64 encoding issue ([9f35949](https://github.com/OneBusAway/python-sdk/commit/9f35949d97c184fb01e3df0bcd4a41bb43b3288d)) -* **test_transform:** optimize base64 encoding ([1b2f58e](https://github.com/OneBusAway/python-sdk/commit/1b2f58e3ba3c76d13714e2b46b79ba2c7580ed29)) -* **test_transform:** update base64 encoding ([b51acbe](https://github.com/OneBusAway/python-sdk/commit/b51acbee92d24aabb3c403b8a53a13a5e2e3c9ca)) -* **test_transform:** update base64 encoding ([62ba4f2](https://github.com/OneBusAway/python-sdk/commit/62ba4f2ddcdb96a030d7b2ca7300b9ea3c35f504)) -* **tests:** update base64 encoding in test_transform ([97a218e](https://github.com/OneBusAway/python-sdk/commit/97a218ee3b9ee49bb19fb38e6b6d07640a3cdc8a)) -* **tests:** update sample_file.txt with newline character ([f0d7644](https://github.com/OneBusAway/python-sdk/commit/f0d76443778e46b4e3eb8f4862190c345da90c6c)) -* **trip:** update trip API endpoint to use trip_id variable ([2602a05](https://github.com/OneBusAway/python-sdk/commit/2602a0537bd4af20306c5f755ec0fb17539153d3)) -* update agency resource URL to use agency_id variable ([d600eb1](https://github.com/OneBusAway/python-sdk/commit/d600eb1146abd67cfda9a4658bf14274083c9568)) -* Update agency resource URL to use agency_id variable ([2719d60](https://github.com/OneBusAway/python-sdk/commit/2719d600837503082d3255825b721d9f577c7824)) -* Update API resource URLs to use agency_id variable ([b9c6499](https://github.com/OneBusAway/python-sdk/commit/b9c6499aaa3dc101b4a35966237aa9a161c7424a)) -* update API resource URLs to use route_id variable ([b374991](https://github.com/OneBusAway/python-sdk/commit/b37499179c139ae5af128589506bc8f4fe4ad7f5)) -* update API resource URLs to use route_id variable ([c80bdae](https://github.com/OneBusAway/python-sdk/commit/c80bdae0c9548d4deb5292911deac4ec45ce4cae)) -* Update API resource URLs to use route_id variable ([8e8f38a](https://github.com/OneBusAway/python-sdk/commit/8e8f38a467fb4515ec66c2d525de4e9355415256)) -* Update API resource URLs to use route_id variable ([b866e83](https://github.com/OneBusAway/python-sdk/commit/b866e832800bf04084bc9e2f025c8d1b326268f3)) -* Update API resource URLs to use route_id variable ([3923494](https://github.com/OneBusAway/python-sdk/commit/39234949a5688648d6c81fbea5c7c59766188cbb)) -* Update API resource URLs to use route_id variable ([0dbbc76](https://github.com/OneBusAway/python-sdk/commit/0dbbc76171b5a26cb80647f4adf9f8c6ceb003dc)) -* update API resource URLs to use stop_id variable ([26cd3ed](https://github.com/OneBusAway/python-sdk/commit/26cd3edafe208a9552eaf9a5f0bee904d8e323cd)) -* Update API resource URLs to use stop_id variable ([ea81ad9](https://github.com/OneBusAway/python-sdk/commit/ea81ad960d16f1bfc4d2b52103bd39b3ad98fbc6)) -* Update API resource URLs to use stop_id variable ([9307bb7](https://github.com/OneBusAway/python-sdk/commit/9307bb78f5229c05fc04e7e172032be12fbfb79c)) -* update API resource URLs to use trip_id variable ([0b100ed](https://github.com/OneBusAway/python-sdk/commit/0b100ed9b37db1b72fc5c0dc70e50c10c369e849)) -* Update API resource URLs to use trip_id variable ([38687b9](https://github.com/OneBusAway/python-sdk/commit/38687b97f2d1ab434576b2e78c5241e70b04d617)) -* Update API resource URLs to use trip_id variable ([c173e75](https://github.com/OneBusAway/python-sdk/commit/c173e751c71520683d8912718e121bd0d62db3ae)) -* Update API resource URLs to use trip_id variable ([7c5fddb](https://github.com/OneBusAway/python-sdk/commit/7c5fddb1668b8049d358c18152b8687a82835110)) -* update API resource URLs to use variable names ([a4f2bac](https://github.com/OneBusAway/python-sdk/commit/a4f2bacb0a5522c1ad5483ebe5b85390ac3aa728)) -* Update API resource URLs to use variable names ([fc93fe5](https://github.com/OneBusAway/python-sdk/commit/fc93fe5d36f1d0a560a41e012a7beec615340996)) -* Update API resource URLs to use variable names ([6d61f7f](https://github.com/OneBusAway/python-sdk/commit/6d61f7f605166cea6ea6655ccee9f829e33f7a8f)) -* update auth_headers method to return an empty dictionary ([ab69500](https://github.com/OneBusAway/python-sdk/commit/ab695005be6ecca41165131a5f3e7167bde6b382)) -* Update auth_headers method to return an empty dictionary ([2d7b81c](https://github.com/OneBusAway/python-sdk/commit/2d7b81ce9ca756909bfd3dc4a67d54cb19ec8aaa)) -* Update auth_headers method to return an empty dictionary ([9d1f2b7](https://github.com/OneBusAway/python-sdk/commit/9d1f2b79d1a7b7bfb2a37937d607629400523fc4)) -* Update main_sync function signature to include return type annotation ([653bb1a](https://github.com/OneBusAway/python-sdk/commit/653bb1a1d1108a1acd1a37b0fa7e6475cfede10a)) -* update the test example ([6f6fa42](https://github.com/OneBusAway/python-sdk/commit/6f6fa42d6143e53ca56b6ca5b22f0d39b3c7b963)) +- **Api:** update agency resource endpoint to use agency_id parameter ([3760fc5](https://github.com/OneBusAway/python-sdk/commit/3760fc5d6969be25dc1228af68d927cae7c41863)) +- **Api:** update API endpoint to use stop_id variable ([91625b8](https://github.com/OneBusAway/python-sdk/commit/91625b8671dd561c340e74c1d8a8ecdd8496fd7b)) +- **api:** update arrival and departure resource endpoints to use stop_id parameter ([f267a9c](https://github.com/OneBusAway/python-sdk/commit/f267a9c20647ee6c2e3b5ab0a4d87862372fdb82)) +- **api:** update tests endpoint to use api_key in param ([446cafd](https://github.com/OneBusAway/python-sdk/commit/446cafd532c627fb46c35887e4d2a32c16e5b726)) +- **client:** update auth_headers method to return an empty dictionary ([8133b61](https://github.com/OneBusAway/python-sdk/commit/8133b61a7776964d965c14276dba02d6639149e2)) +- **EndPoint:** update agency API endpoint to use agency_id variable ([6abcaa6](https://github.com/OneBusAway/python-sdk/commit/6abcaa66d066b5fb5fab379d5d26d12f4c63c806)) +- **examples:** remove commented code and unused imports ([ffe2774](https://github.com/OneBusAway/python-sdk/commit/ffe27745b20e593f2770285d122bf53128b374a0)) +- **route:** update route API endpoint to use route_id variable ([9857e55](https://github.com/OneBusAway/python-sdk/commit/9857e558f2f0ad355ca90af74978e23d48ba0948)) +- **route:** update route resource endpoint to use route_id parameter ([643d71d](https://github.com/OneBusAway/python-sdk/commit/643d71da89b6e02275b032e6bd7a78b17066f06b)) +- **test_transform:** fix base64 encoding issue ([fefa1fb](https://github.com/OneBusAway/python-sdk/commit/fefa1fb8d02dcc8c9b536f11cd3388b3ef3d8055)) +- **test_transform:** fix base64 encoding issue ([9f35949](https://github.com/OneBusAway/python-sdk/commit/9f35949d97c184fb01e3df0bcd4a41bb43b3288d)) +- **test_transform:** optimize base64 encoding ([1b2f58e](https://github.com/OneBusAway/python-sdk/commit/1b2f58e3ba3c76d13714e2b46b79ba2c7580ed29)) +- **test_transform:** update base64 encoding ([b51acbe](https://github.com/OneBusAway/python-sdk/commit/b51acbee92d24aabb3c403b8a53a13a5e2e3c9ca)) +- **test_transform:** update base64 encoding ([62ba4f2](https://github.com/OneBusAway/python-sdk/commit/62ba4f2ddcdb96a030d7b2ca7300b9ea3c35f504)) +- **tests:** update base64 encoding in test_transform ([97a218e](https://github.com/OneBusAway/python-sdk/commit/97a218ee3b9ee49bb19fb38e6b6d07640a3cdc8a)) +- **tests:** update sample_file.txt with newline character ([f0d7644](https://github.com/OneBusAway/python-sdk/commit/f0d76443778e46b4e3eb8f4862190c345da90c6c)) +- **trip:** update trip API endpoint to use trip_id variable ([2602a05](https://github.com/OneBusAway/python-sdk/commit/2602a0537bd4af20306c5f755ec0fb17539153d3)) +- update agency resource URL to use agency_id variable ([d600eb1](https://github.com/OneBusAway/python-sdk/commit/d600eb1146abd67cfda9a4658bf14274083c9568)) +- Update agency resource URL to use agency_id variable ([2719d60](https://github.com/OneBusAway/python-sdk/commit/2719d600837503082d3255825b721d9f577c7824)) +- Update API resource URLs to use agency_id variable ([b9c6499](https://github.com/OneBusAway/python-sdk/commit/b9c6499aaa3dc101b4a35966237aa9a161c7424a)) +- update API resource URLs to use route_id variable ([b374991](https://github.com/OneBusAway/python-sdk/commit/b37499179c139ae5af128589506bc8f4fe4ad7f5)) +- update API resource URLs to use route_id variable ([c80bdae](https://github.com/OneBusAway/python-sdk/commit/c80bdae0c9548d4deb5292911deac4ec45ce4cae)) +- Update API resource URLs to use route_id variable ([8e8f38a](https://github.com/OneBusAway/python-sdk/commit/8e8f38a467fb4515ec66c2d525de4e9355415256)) +- Update API resource URLs to use route_id variable ([b866e83](https://github.com/OneBusAway/python-sdk/commit/b866e832800bf04084bc9e2f025c8d1b326268f3)) +- Update API resource URLs to use route_id variable ([3923494](https://github.com/OneBusAway/python-sdk/commit/39234949a5688648d6c81fbea5c7c59766188cbb)) +- Update API resource URLs to use route_id variable ([0dbbc76](https://github.com/OneBusAway/python-sdk/commit/0dbbc76171b5a26cb80647f4adf9f8c6ceb003dc)) +- update API resource URLs to use stop_id variable ([26cd3ed](https://github.com/OneBusAway/python-sdk/commit/26cd3edafe208a9552eaf9a5f0bee904d8e323cd)) +- Update API resource URLs to use stop_id variable ([ea81ad9](https://github.com/OneBusAway/python-sdk/commit/ea81ad960d16f1bfc4d2b52103bd39b3ad98fbc6)) +- Update API resource URLs to use stop_id variable ([9307bb7](https://github.com/OneBusAway/python-sdk/commit/9307bb78f5229c05fc04e7e172032be12fbfb79c)) +- update API resource URLs to use trip_id variable ([0b100ed](https://github.com/OneBusAway/python-sdk/commit/0b100ed9b37db1b72fc5c0dc70e50c10c369e849)) +- Update API resource URLs to use trip_id variable ([38687b9](https://github.com/OneBusAway/python-sdk/commit/38687b97f2d1ab434576b2e78c5241e70b04d617)) +- Update API resource URLs to use trip_id variable ([c173e75](https://github.com/OneBusAway/python-sdk/commit/c173e751c71520683d8912718e121bd0d62db3ae)) +- Update API resource URLs to use trip_id variable ([7c5fddb](https://github.com/OneBusAway/python-sdk/commit/7c5fddb1668b8049d358c18152b8687a82835110)) +- update API resource URLs to use variable names ([a4f2bac](https://github.com/OneBusAway/python-sdk/commit/a4f2bacb0a5522c1ad5483ebe5b85390ac3aa728)) +- Update API resource URLs to use variable names ([fc93fe5](https://github.com/OneBusAway/python-sdk/commit/fc93fe5d36f1d0a560a41e012a7beec615340996)) +- Update API resource URLs to use variable names ([6d61f7f](https://github.com/OneBusAway/python-sdk/commit/6d61f7f605166cea6ea6655ccee9f829e33f7a8f)) +- update auth_headers method to return an empty dictionary ([ab69500](https://github.com/OneBusAway/python-sdk/commit/ab695005be6ecca41165131a5f3e7167bde6b382)) +- Update auth_headers method to return an empty dictionary ([2d7b81c](https://github.com/OneBusAway/python-sdk/commit/2d7b81ce9ca756909bfd3dc4a67d54cb19ec8aaa)) +- Update auth_headers method to return an empty dictionary ([9d1f2b7](https://github.com/OneBusAway/python-sdk/commit/9d1f2b79d1a7b7bfb2a37937d607629400523fc4)) +- Update main_sync function signature to include return type annotation ([653bb1a](https://github.com/OneBusAway/python-sdk/commit/653bb1a1d1108a1acd1a37b0fa7e6475cfede10a)) +- update the test example ([6f6fa42](https://github.com/OneBusAway/python-sdk/commit/6f6fa42d6143e53ca56b6ca5b22f0d39b3c7b963)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d899759..9abb25c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,13 @@ ### With Rye -We use [Rye](https://rye.astral.sh/) to manage dependencies so we highly recommend [installing it](https://rye.astral.sh/guide/installation/) as it will automatically provision a Python environment with the expected Python version. +We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: -After installing Rye, you'll just have to run this command: +```sh +$ ./scripts/bootstrap +``` + +Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: ```sh $ rye sync --all-features @@ -13,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 @@ -31,25 +34,25 @@ $ pip install -r requirements-dev.lock ## Modifying/Adding code -Most of the SDK is generated code, and any modified code will be overridden on the next generation. The -`src/onebusaway/lib/` and `examples/` directories are exceptions and will never be overridden. +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may +result in merge conflicts between manual patches and changes from the generator. The generator will never +modify the contents of the `src/onebusaway/lib/` and `examples/` directories. ## Adding and running examples -All files in the `examples/` directory are not modified by the Stainless generator and can be freely edited or -added to. +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. -```bash +```py # add an example to examples/.py #!/usr/bin/env -S rye run python … ``` -``` -chmod +x examples/.py +```sh +$ chmod +x examples/.py # run the example against your api -./examples/.py +$ ./examples/.py ``` ## Using the repository from source @@ -58,8 +61,8 @@ If you’d like to use the repository from source, you can either install from g To install via git: -```bash -pip install git+ssh://git@github.com/OneBusAway/python-sdk.git +```sh +$ pip install git+ssh://git@github.com/OneBusAway/python-sdk.git ``` Alternatively, you can build from source and install the wheel file: @@ -68,29 +71,28 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz To create a distributable version of the library, all you have to do is run this command: -```bash -rye build +```sh +$ rye build # or -python -m build +$ python -m build ``` Then to install: ```sh -pip install ./path-to-wheel-file.whl +$ 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. +Most tests require you to [set up a mock server](https://github.com/dgellow/steady) against the OpenAPI spec to run the tests. -```bash -# you will need npm installed -npx prism mock path/to/your/openapi.yml +```sh +$ ./scripts/mock ``` -```bash -rye run pytest +```sh +$ ./scripts/test ``` ## Linting and formatting @@ -100,14 +102,14 @@ This repository uses [ruff](https://github.com/astral-sh/ruff) and To lint: -```bash -rye run lint +```sh +$ ./scripts/lint ``` To format and fix all ruff issues automatically: -```bash -rye run format +```sh +$ ./scripts/format ``` ## Publishing and releases diff --git a/LICENSE b/LICENSE index 9e6f6ec2..26680baf 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 Onebusaway SDK + Copyright 2026 Onebusaway SDK 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 1d01edc0..1d0febd0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # OneBusAway SDK Python API library -[![PyPI version](https://img.shields.io/pypi/v/onebusaway.svg)](https://pypi.org/project/onebusaway/) + +[![PyPI version](https://img.shields.io/pypi/v/onebusaway.svg?label=pypi%20(stable))](https://pypi.org/project/onebusaway/) + + +The Onebusaway SDK Python library provides convenient access to the Onebusaway SDK REST API from any Python 3.9+ -The OneBusAway SDK Python library provides convenient access to the OneBusAway SDK REST API from any Python 3.7+ 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). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation @@ -16,7 +19,7 @@ The REST API documentation can be found on [developer.onebusaway.org](https://de ```sh # install from PyPI -pip install --pre onebusaway +pip install onebusaway ``` ## Usage @@ -28,11 +31,10 @@ import os from onebusaway import OnebusawaySDK client = OnebusawaySDK( - # This is the default and can be omitted - api_key=os.environ.get("ONEBUSAWAY_API_KEY"), + api_key=os.environ.get("ONEBUSAWAY_API_KEY"), # This is the default and can be omitted ) -current_time_retrieve_response = client.current_time.retrieve() +current_time = client.current_time.retrieve() ``` While you can provide an `api_key` keyword argument, @@ -50,13 +52,12 @@ import asyncio from onebusaway import AsyncOnebusawaySDK client = AsyncOnebusawaySDK( - # This is the default and can be omitted - api_key=os.environ.get("ONEBUSAWAY_API_KEY"), + api_key=os.environ.get("ONEBUSAWAY_API_KEY"), # This is the default and can be omitted ) async def main() -> None: - current_time_retrieve_response = await client.current_time.retrieve() + current_time = await client.current_time.retrieve() asyncio.run(main()) @@ -64,6 +65,37 @@ 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 PyPI +pip install onebusaway[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from onebusaway import DefaultAioHttpClient +from onebusaway import AsyncOnebusawaySDK + + +async def main() -> None: + async with AsyncOnebusawaySDK( + api_key=os.environ.get("ONEBUSAWAY_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + current_time = await client.current_time.retrieve() + + +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: @@ -101,7 +133,7 @@ except onebusaway.APIStatusError as e: print(e.response) ``` -Error codes are as followed: +Error codes are as follows: | Status Code | Error Type | | ----------- | -------------------------- | @@ -138,7 +170,7 @@ client.with_options(max_retries=5).current_time.retrieve() ### 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 onebusaway import OnebusawaySDK @@ -168,12 +200,14 @@ Note that requests that time out are [retried twice by default](#retries). We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. -You can enable logging by setting the environment variable `ONEBUSAWAY_SDK_LOG` to `debug`. +You can enable logging by setting the environment variable `ONEBUSAWAY_SDK_LOG` to `info`. ```shell -$ export ONEBUSAWAY_SDK_LOG=debug +$ export ONEBUSAWAY_SDK_LOG=info ``` +Or to `debug` for more verbose logging. + ### How to tell whether `None` means `null` or missing In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: @@ -230,8 +264,7 @@ If you need to access undocumented endpoints, params, or response properties, th #### Undocumented endpoints To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) will be respected when making this -request. +http verbs. Options on the client will be respected (such as retries) when making this request. ```py import httpx @@ -260,18 +293,19 @@ can also get all the extra fields on the Pydantic model as a dict with You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: -- Support for proxies -- Custom transports +- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) +- Custom [transports](https://www.python-httpx.org/advanced/transports/) - Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality ```python +import httpx from onebusaway import OnebusawaySDK, DefaultHttpxClient client = OnebusawaySDK( # Or use the `ONEBUSAWAY_SDK_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( - proxies="http://my.test.proxy.example.com", + proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), ), ) @@ -287,18 +321,43 @@ client.with_options(http_client=DefaultHttpxClient(...)) By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +```py +from onebusaway import OnebusawaySDK + +with OnebusawaySDK() as client: + # make requests here + ... + +# HTTP client is now closed +``` + ## Versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 3. Changes that we do not expect to impact the vast majority of users in practice. 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/OneBusAway/python-sdk/issues) with questions, bugs, or suggestions. +### Determining the installed version + +If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version. + +You can determine the version that is being used at runtime with: + +```py +import onebusaway +print(onebusaway.__version__) +``` + ## Requirements -Python 3.7 or higher. +Python 3.9 or higher. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md index 62ac0161..8b844a0d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure @@ -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 Onebusaway SDK please follow the respective company's security reporting guidelines. +or products provided by Onebusaway SDK, please follow the respective company's security reporting guidelines. ### Onebusaway SDK Terms and Policies -Please contact info@onebusaway.org for any questions or concerns regarding security of our services. +Please contact info@onebusaway.org for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index 8404d9c9..7a627caf 100644 --- a/api.md +++ b/api.md @@ -9,12 +9,12 @@ from onebusaway.types import References, ResponseWrapper Types: ```python -from onebusaway.types import AgenciesWithCoverageRetrieveResponse +from onebusaway.types import AgenciesWithCoverageListResponse ``` Methods: -- client.agencies_with_coverage.retrieve() -> AgenciesWithCoverageRetrieveResponse +- client.agencies_with_coverage.list() -> AgenciesWithCoverageListResponse # Agency @@ -69,12 +69,12 @@ Methods: Types: ```python -from onebusaway.types import StopsForLocationRetrieveResponse +from onebusaway.types import StopsForLocationListResponse ``` Methods: -- client.stops_for_location.retrieve(\*\*params) -> StopsForLocationRetrieveResponse +- client.stops_for_location.list(\*\*params) -> StopsForLocationListResponse # StopsForRoute @@ -88,6 +88,18 @@ Methods: - client.stops_for_route.list(route_id, \*\*params) -> StopsForRouteListResponse +# StopsForAgency + +Types: + +```python +from onebusaway.types import StopsForAgencyListResponse +``` + +Methods: + +- client.stops_for_agency.list(agency_id) -> StopsForAgencyListResponse + # Stop Types: @@ -136,6 +148,54 @@ Methods: - client.route.retrieve(route_id) -> RouteRetrieveResponse +# RouteIDsForAgency + +Types: + +```python +from onebusaway.types import RouteIDsForAgencyListResponse +``` + +Methods: + +- client.route_ids_for_agency.list(agency_id) -> RouteIDsForAgencyListResponse + +# RoutesForLocation + +Types: + +```python +from onebusaway.types import RoutesForLocationListResponse +``` + +Methods: + +- client.routes_for_location.list(\*\*params) -> RoutesForLocationListResponse + +# RoutesForAgency + +Types: + +```python +from onebusaway.types import RoutesForAgencyListResponse +``` + +Methods: + +- client.routes_for_agency.list(agency_id) -> RoutesForAgencyListResponse + +# ScheduleForRoute + +Types: + +```python +from onebusaway.types import ScheduleForRouteRetrieveResponse +``` + +Methods: + +- client.schedule_for_route.retrieve(route_id, \*\*params) -> ScheduleForRouteRetrieveResponse + # ArrivalAndDeparture Types: @@ -166,12 +226,12 @@ Methods: Types: ```python -from onebusaway.types import TripsForLocationRetrieveResponse +from onebusaway.types import TripsForLocationListResponse ``` Methods: -- client.trips_for_location.retrieve(\*\*params) -> TripsForLocationRetrieveResponse +- client.trips_for_location.list(\*\*params) -> TripsForLocationListResponse # TripDetails @@ -196,3 +256,75 @@ from onebusaway.types import TripForVehicleRetrieveResponse Methods: - client.trip_for_vehicle.retrieve(vehicle_id, \*\*params) -> TripForVehicleRetrieveResponse + +# TripsForRoute + +Types: + +```python +from onebusaway.types import TripsForRouteListResponse +``` + +Methods: + +- client.trips_for_route.list(route_id, \*\*params) -> TripsForRouteListResponse + +# ReportProblemWithStop + +Methods: + +- client.report_problem_with_stop.retrieve(stop_id, \*\*params) -> ResponseWrapper + +# ReportProblemWithTrip + +Methods: + +- client.report_problem_with_trip.retrieve(trip_id, \*\*params) -> ResponseWrapper + +# SearchForStop + +Types: + +```python +from onebusaway.types import SearchForStopListResponse +``` + +Methods: + +- client.search_for_stop.list(\*\*params) -> SearchForStopListResponse + +# SearchForRoute + +Types: + +```python +from onebusaway.types import SearchForRouteListResponse +``` + +Methods: + +- client.search_for_route.list(\*\*params) -> SearchForRouteListResponse + +# Block + +Types: + +```python +from onebusaway.types import BlockRetrieveResponse +``` + +Methods: + +- client.block.retrieve(block_id) -> BlockRetrieveResponse + +# Shape + +Types: + +```python +from onebusaway.types import ShapeRetrieveResponse +``` + +Methods: + +- client.shape.retrieve(shape_id) -> ShapeRetrieveResponse diff --git a/bin/check-release-environment b/bin/check-release-environment index 7e1bf0e9..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The ONEBUSAWAY_SDK_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + 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[@]} diff --git a/bin/publish-pypi b/bin/publish-pypi index 05bfccbb..826054e9 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -3,7 +3,4 @@ set -eux mkdir -p dist rye build --clean -# Patching importlib-metadata version until upstream library version is updated -# https://github.com/pypa/twine/issues/977#issuecomment-2189800841 -"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' rye publish --yes --token=$PYPI_TOKEN diff --git a/examples/agencies_with_coverage.py b/examples/agencies_with_coverage.py index 0e239ed5..87a5fb3c 100644 --- a/examples/agencies_with_coverage.py +++ b/examples/agencies_with_coverage.py @@ -17,5 +17,5 @@ # Create a new instance of the OneBusAway SDK with the settings we loaded. oba = OnebusawaySDK(**settings) -response = oba.agencies_with_coverage.retrieve() +response = oba.agencies_with_coverage.list() pprint(response.data) diff --git a/examples/block.py b/examples/block.py new file mode 100644 index 00000000..1d819bd4 --- /dev/null +++ b/examples/block.py @@ -0,0 +1,21 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +# Load settings from .env file, if it exists. If not, we'll use the +# Puget Sound server URL (which is also the default in the SDK) and +# the 'TEST' API key. +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +# Create a new instance of the OneBusAway SDK with the settings we loaded. +oba = OnebusawaySDK(**settings) + +block_id = "1_7310845" +response = oba.block.retrieve(block_id) +if response and response.data: + print(response.data.entry) diff --git a/examples/helpers/__init__.py b/examples/helpers/__init__.py new file mode 100644 index 00000000..76b6e5e4 --- /dev/null +++ b/examples/helpers/__init__.py @@ -0,0 +1,3 @@ +from .load_env import load_env, load_settings + +__all__ = ["load_settings", "load_env"] diff --git a/examples/routes_for_agency.py b/examples/routes_for_agency.py new file mode 100644 index 00000000..960f0fd4 --- /dev/null +++ b/examples/routes_for_agency.py @@ -0,0 +1,24 @@ +from pprint import pprint + +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +# Load settings from .env file, if it exists. If not, we'll use the +# Puget Sound server URL (which is also the default in the SDK) and +# the 'TEST' API key. +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +# Create a new instance of the OneBusAway SDK with the settings we loaded. +oba = OnebusawaySDK(**settings) + +agency_id = "40" +response = oba.routes_for_agency.list(agency_id) + +if response and response.data: + pprint(response.data) diff --git a/examples/routes_ids_for_agency.py b/examples/routes_ids_for_agency.py new file mode 100644 index 00000000..c5f6547a --- /dev/null +++ b/examples/routes_ids_for_agency.py @@ -0,0 +1,23 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +# Load settings from .env file, if it exists. If not, we'll use the +# Puget Sound server URL (which is also the default in the SDK) and +# the 'TEST' API key. +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +# Create a new instance of the OneBusAway SDK with the settings we loaded. +oba = OnebusawaySDK(**settings) + +agency_id = "40" # Link Light Rail in the Seattle area. + +route_ids = oba.route_ids_for_agency.list(agency_id) +if route_ids.data and route_ids.data.list: + for route_id in route_ids.data.list: + print(route_id) diff --git a/examples/schedule_for_route.py b/examples/schedule_for_route.py new file mode 100644 index 00000000..530a3ca1 --- /dev/null +++ b/examples/schedule_for_route.py @@ -0,0 +1,21 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +# Load settings from .env file, if it exists. If not, we'll use the +# Puget Sound server URL (which is also the default in the SDK) and +# the 'TEST' API key. +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +# Create a new instance of the OneBusAway SDK with the settings we loaded. +oba = OnebusawaySDK(**settings) + +route_id = "1_100224" +response = oba.schedule_for_route.retrieve(route_id) +if response and response.data: + print(response.data.entry.stop_trip_groupings[0]) diff --git a/examples/search_for_route.py b/examples/search_for_route.py new file mode 100644 index 00000000..94f22f96 --- /dev/null +++ b/examples/search_for_route.py @@ -0,0 +1,20 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +oba = OnebusawaySDK(**settings) + +# Search for routes with the word "crystal" in them. +search_input = "crystal" + +# Retrieve the search results. +response = oba.search_for_route.list(input=search_input) + +print(response.data) diff --git a/examples/search_for_stop.py b/examples/search_for_stop.py new file mode 100644 index 00000000..d969ffcc --- /dev/null +++ b/examples/search_for_stop.py @@ -0,0 +1,20 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +oba = OnebusawaySDK(**settings) + +# Search for stops with the word "crystal" in them. +search_input = "crystal" + +# Retrieve the search results. +response = oba.search_for_stop.list(input=search_input, max_count=5) + +print(response.data) diff --git a/examples/shape.py b/examples/shape.py new file mode 100644 index 00000000..6a12f225 --- /dev/null +++ b/examples/shape.py @@ -0,0 +1,21 @@ +from helpers.load_env import load_settings + +from onebusaway import OnebusawaySDK + +# Load settings from .env file, if it exists. If not, we'll use the +# Puget Sound server URL (which is also the default in the SDK) and +# the 'TEST' API key. +settings = load_settings( + { + "api_key": "TEST", + "base_url": "https://api.pugetsound.onebusaway.org/", + } +) + +# Create a new instance of the OneBusAway SDK with the settings we loaded. +oba = OnebusawaySDK(**settings) + +shape_id = "1_10002005" +response = oba.shape.retrieve(shape_id) +if response and response.data: + print(response.data.entry) diff --git a/examples/stops_for_location.py b/examples/stops_for_location.py index 4788e137..283aca59 100644 --- a/examples/stops_for_location.py +++ b/examples/stops_for_location.py @@ -1,3 +1,5 @@ +from typing import Any + from helpers.load_env import load_settings from onebusaway import OnebusawaySDK @@ -15,8 +17,7 @@ # Create a new instance of the OneBusAway SDK with the settings we loaded. oba = OnebusawaySDK(**settings) -space_needle_stops = oba.stops_for_location.retrieve( - key="TEST", # TODO FIXME: I shouldn't have to specify the API key here. +space_needle_stops = oba.stops_for_location.list( lat=47.6205, lon=-122.3493, ) @@ -26,18 +27,19 @@ # make it easy to look up routes by ID. reference_map = {} -for route in references.routes: - reference_map[route.id] = route +for ref_route in references.routes: + reference_map[ref_route.id] = ref_route for stop in stops: print(f"{stop.name} ({stop.lat}, {stop.lon})") print(" Routes:") for route_id in stop.route_ids: - route = reference_map[route_id] + # TODO: add type to route + route: Any = reference_map[route_id] # type: ignore # Get a string that looks like "D Line - Blue Ridge/Crown Hill - Ballard - Downtown Seattle" - description = [route.null_safe_short_name, route.description] - description = [e for e in description if e] - description = " - ".join(description) - print(f" {description}") + description_list = [route.null_safe_short_name, route.description] # type: ignore + description_list = [e for e in description_list if e] # type: ignore + description_str = " - ".join(description_list) # type: ignore + print(f" {description_str}") diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 635eec8e..00000000 --- a/mypy.ini +++ /dev/null @@ -1,47 +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. -exclude = ^(src/onebusaway/_files\.py|_dev/.*\.py)$ - -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 - -# 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 6db0b9e6..27beb696 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,32 @@ [project] name = "onebusaway" -version = "0.1.0-alpha.10" +version = "1.23.0" description = "The official Python library for the onebusaway-sdk API" dynamic = ["readme"] license = "Apache-2.0" authors = [ { name = "Onebusaway SDK", email = "info@onebusaway.org" }, ] -dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.7, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", - "cached-property; python_version < '3.8'", +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.14, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] -requires-python = ">= 3.7" + +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", - "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", @@ -36,20 +36,19 @@ classifiers = [ "License :: OSI Approved :: Apache Software License" ] - - [project.urls] Homepage = "https://github.com/OneBusAway/python-sdk" Repository = "https://github.com/OneBusAway/python-sdk" - +[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.359", - "mypy", + "pyright==1.1.399", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", @@ -59,7 +58,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -67,18 +66,21 @@ format = { chain = [ "format:ruff", "format:docs", "fix:ruff", + # run formatting again to fix any inconsistencies when imports are stripped + "format:ruff", ]} -"format:black" = "black ." -"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" -"format:isort" = "isort ." "lint" = { chain = [ "check:ruff", "typecheck", + "check:importable", ]} -"check:ruff" = "ruff ." -"fix:ruff" = "ruff --fix ." +"check:ruff" = "ruff check ." +"fix:ruff" = "ruff check --fix ." + +"check:importable" = "python -c 'import onebusaway'" typecheck = { chain = [ "typecheck:pyright", @@ -89,7 +91,7 @@ typecheck = { chain = [ "typecheck:mypy" = "mypy ." [build-system] -requires = ["hatchling", "hatch-fancy-pypi-readme"] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" [tool.hatch.build] @@ -126,15 +128,12 @@ path = "README.md" pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' replacement = '[\1](https://github.com/OneBusAway/python-sdk/tree/main/\g<2>)' -[tool.black] -line-length = 120 -target-version = ["py37"] - [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" filterwarnings = [ "error" ] @@ -144,24 +143,82 @@ 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.7" +pythonVersion = "3.9" exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true +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/onebusaway/_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 + +[tool.ruff.lint] select = [ # isort "I", @@ -169,6 +226,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -177,7 +236,7 @@ select = [ "T201", "T203", # misuse of typing.TYPE_CHECKING - "TCH004", + "TC004", # import rules "TID251", ] @@ -190,10 +249,8 @@ unfixable = [ "T201", "T203", ] -ignore-init-module-imports = true -[tool.ruff.format] -docstring-code-format = true +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" @@ -205,7 +262,7 @@ combine-as-imports = true extra-standard-library = ["typing_extensions"] known-first-party = ["onebusaway", "tests"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "bin/**.py" = ["T201", "T203"] "scripts/**.py" = ["T201", "T203"] "tests/**.py" = ["T201", "T203"] diff --git a/requirements-dev.lock b/requirements-dev.lock index bc4b7473..8bdb7bf3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,97 +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 onebusaway +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via onebusaway -argcomplete==3.1.2 +argcomplete==3.6.3 # via nox -attrs==23.1.0 - # via pytest -certifi==2023.7.22 +async-timeout==5.0.1 + # via aiohttp +attrs==25.4.0 + # via aiohttp + # via nox +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 onebusaway -exceptiongroup==1.1.3 +exceptiongroup==1.3.1 # via anyio -filelock==3.12.4 + # via pytest +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.25.2 +httpx==0.28.1 + # via httpx-aiohttp # via onebusaway # via respx -idna==3.4 +httpx-aiohttp==0.1.12 + # via onebusaway +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.10.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 -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.3.0 +pluggy==1.6.0 # via pytest -py==1.11.0 - # via pytest -pydantic==2.7.1 +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via onebusaway -pydantic-core==2.18.2 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich -pyright==1.1.364 -pytest==7.1.1 +pyright==1.1.399 +pytest==8.4.2 # via pytest-asyncio -pytest-asyncio==0.21.1 -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.20.2 -rich==13.7.1 -ruff==0.1.9 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +respx==0.22.0 +rich==14.2.0 +ruff==0.14.13 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio - # via httpx +sniffio==1.3.1 # via onebusaway -time-machine==2.9.0 -tomli==2.0.1 +time-machine==2.19.0 +tomli==2.4.0 + # via dependency-groups # via mypy + # via nox # via pytest -typing-extensions==4.8.0 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup + # via multidict # via mypy # via onebusaway # via pydantic # via pydantic-core -virtualenv==20.24.5 + # via pyright + # 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 5df36b84..6724bb11 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,37 +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 onebusaway +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.1 # via httpx # via onebusaway -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 onebusaway -exceptiongroup==1.1.3 +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.25.2 +httpx==0.28.1 + # via httpx-aiohttp + # via onebusaway +httpx-aiohttp==0.1.12 # via onebusaway -idna==3.4 +idna==3.11 # via anyio # via httpx -pydantic==2.7.1 + # via yarl +multidict==6.7.0 + # via aiohttp + # via yarl +propcache==0.4.1 + # via aiohttp + # via yarl +pydantic==2.12.5 # via onebusaway -pydantic-core==2.18.2 +pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio - # via httpx +sniffio==1.3.1 # via onebusaway -typing-extensions==4.8.0 +typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup + # via multidict # via onebusaway # 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 8c5c60eb..b430fee3 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -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 4656dbb3..ed21d544 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,9 +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 onebusaway' - diff --git a/scripts/mock b/scripts/mock index f5861576..58e46285 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.4 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.19.7 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.19.7 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi 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.4 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.19.7 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index b3ace901..39704647 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ 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 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,21 +36,26 @@ 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" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady 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 -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.7 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" + +echo "==> Running Pydantic v1 tests" +rye run nox -s test-pydantic-v1 -- "$@" diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py index 37b3d94f..0cf2bd2f 100644 --- a/scripts/utils/ruffen-docs.py +++ b/scripts/utils/ruffen-docs.py @@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str: with _collect_error(match): code = format_code_block(code) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" def _pycon_match(match: Match[str]) -> str: code = "" @@ -97,7 +97,7 @@ def finish_fragment() -> None: def _md_pycon_match(match: Match[str]) -> str: code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) - return f'{match["before"]}{code}{match["after"]}' + return f"{match['before']}{code}{match['after']}" src = MD_RE.sub(_md_match, src) src = MD_PYCON_RE.sub(_md_pycon_match, src) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..33686ccc --- /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/open-transit-python/$SHA/$FILENAME'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/onebusaway/__init__.py b/src/onebusaway/__init__.py index b078feab..fb5549b4 100644 --- a/src/onebusaway/__init__.py +++ b/src/onebusaway/__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, 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,6 +48,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", + "Omit", + "omit", "OnebusawaySDKError", "APIError", "APIStatusError", @@ -75,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/onebusaway/_base_client.py b/src/onebusaway/_base_client.py index d17404e4..1990426b 100644 --- a/src/onebusaway/_base_client.py +++ b/src/onebusaway/_base_client.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import json import time import uuid @@ -35,14 +36,13 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -50,19 +50,19 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, + BinaryTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import 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, @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -101,7 +102,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 @@ -118,32 +123,48 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( self, *, url: URL, - ) -> None: - ... + ) -> None: ... @overload def __init__( self, *, params: Query, - ) -> None: - ... + ) -> None: ... + @overload def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + 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})" + class BasePage(GenericModel, Generic[_T]): """ @@ -166,8 +187,7 @@ def has_next_page(self) -> bool: return False return self.next_page_info() is not None - def next_page_info(self) -> Optional[PageInfo]: - ... + def next_page_info(self) -> Optional[PageInfo]: ... def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] ... @@ -191,6 +211,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") @@ -203,6 +236,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -288,6 +324,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -327,9 +366,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -342,9 +378,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -352,9 +385,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -402,14 +432,7 @@ def _make_status_error( ) -> _exceptions.APIStatusError: raise NotImplementedError() - def _remaining_retries( - self, - remaining_retries: Optional[int], - options: FinalRequestOptions, - ) -> int: - return remaining_retries if remaining_retries is not None else options.get_max_retries(self.max_retries) - - def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers: + def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: custom_headers = options.headers or {} headers_dict = _merge_mappings(self.default_headers, custom_headers) self._validate_headers(headers_dict, custom_headers) @@ -418,8 +441,20 @@ def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers: 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. + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-stainless-retry-count" not in lower_custom_headers: + headers["x-stainless-retry-count"] = str(retries_taken) + if "x-stainless-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-stainless-read-timeout"] = str(timeout) return headers @@ -442,10 +477,23 @@ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: def _build_request( self, options: FinalRequestOptions, + *, + 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 @@ -457,7 +505,7 @@ def _build_request( else: raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") - headers = self._build_headers(options) + headers = self._build_headers(options, retries_taken=retries_taken) params = _merge_mappings(self.default_query, options.params) content_type = headers.get("Content-Type") files = options.files @@ -491,19 +539,42 @@ def _build_request( if not files: files = cast(HttpxRequestFiles, ForceMultipartDict()) + prepared_url = self._prepare_url(options.url) + if "_" in prepared_url.host: + # 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, timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, method=options.method, - url=self._prepare_url(options.url), + url=prepared_url, # the `Query` type that we use is incompatible with qs' # `Params` type as it needs to be typed as `Mapping[str, object]` # 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, - files=files, **kwargs, ) @@ -547,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) @@ -686,7 +757,8 @@ def _calculate_retry_timeout( if retry_after is not None and 0 < retry_after <= 60: return retry_after - nb_retries = max_retries - remaining_retries + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) # Apply exponential backoff, but not more than the max. sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) @@ -757,6 +829,9 @@ def __init__(self, **kwargs: Any) -> None: class SyncHttpxClientWrapper(DefaultHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: self.close() except Exception: @@ -773,44 +848,12 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + 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, _strict_response_validation: bool, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -831,12 +874,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -846,10 +886,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -899,189 +935,163 @@ def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[True], stream_cls: Type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def request( self, cast_to: Type[ResponseT], options: FinalRequestOptions, - remaining_retries: Optional[int] = None, *, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload 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: - ... + ) -> ResponseT | _StreamT: ... 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: - return self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) + cast_to = self._maybe_override_cast_to(cast_to, options) - def _request( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - remaining_retries: int | None, - 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) + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - self._prepare_request(request) + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = 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) + self._prepare_request(request) - log.debug("Sending HTTP Request: %s %s", request.method, request.url) + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth - 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 retries > 0: - return self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - 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 retries > 0: - return self._retry_request( - input_options, - cast_to, - retries, - 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 retries > 0 and self._should_retry(err.response): - err.response.close() - return self._retry_request( - input_options, - cast_to, - retries, - 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, response=response, stream=stream, stream_cls=stream_cls, + retries_taken=retries_taken, ) - def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_StreamT] | None, - ) -> ResponseT | _StreamT: - remaining = remaining_retries - 1 - if remaining == 1: + 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) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, 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, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - def _process_response( self, *, @@ -1090,10 +1100,18 @@ def _process_response( response: httpx.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, ) -> 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}") @@ -1107,6 +1125,7 @@ def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) @@ -1120,6 +1139,7 @@ def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ) if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): return cast(ResponseT, api_response) @@ -1152,8 +1172,7 @@ def get( cast_to: Type[ResponseT], options: RequestOptions = {}, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload def get( @@ -1164,8 +1183,7 @@ def get( options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def get( @@ -1176,8 +1194,7 @@ def get( options: RequestOptions = {}, stream: bool, stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - ... + ) -> ResponseT | _StreamT: ... def get( self, @@ -1200,11 +1217,11 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload def post( @@ -1213,12 +1230,12 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], stream_cls: type[_StreamT], - ) -> _StreamT: - ... + ) -> _StreamT: ... @overload def post( @@ -1227,12 +1244,12 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, stream_cls: type[_StreamT] | None = None, - ) -> ResponseT | _StreamT: - ... + ) -> ResponseT | _StreamT: ... def post( self, @@ -1240,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)) @@ -1256,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( @@ -1267,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) @@ -1281,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( @@ -1308,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 @@ -1316,12 +1400,19 @@ 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): def __del__(self) -> None: + if self.is_closed: + return + try: # TODO(someday): support non asyncio runtimes here asyncio.get_running_loop().create_task(self.aclose()) @@ -1340,43 +1431,11 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, + 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, ) -> None: - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1398,11 +1457,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1412,10 +1468,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, - limits=limits, - follow_redirects=True, ) def is_closed(self) -> bool: @@ -1464,9 +1516,7 @@ async def request( options: FinalRequestOptions, *, stream: Literal[False] = False, - remaining_retries: Optional[int] = None, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def request( @@ -1476,9 +1526,7 @@ async def request( *, stream: Literal[True], stream_cls: type[_AsyncStreamT], - remaining_retries: Optional[int] = None, - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def request( @@ -1488,9 +1536,7 @@ async def request( *, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def request( self, @@ -1499,148 +1545,137 @@ async def request( *, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, - remaining_retries: Optional[int] = None, - ) -> ResponseT | _AsyncStreamT: - return await self._request( - cast_to=cast_to, - options=options, - stream=stream, - stream_cls=stream_cls, - remaining_retries=remaining_retries, - ) - - async def _request( - self, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - remaining_retries: int | None, ) -> 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) - retries = self._remaining_retries(remaining_retries, options) - request = self._build_request(options) - 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 retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries, - 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) + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects - if retries > 0: - return await self._retry_request( - input_options, - cast_to, - retries, - stream=stream, - stream_cls=stream_cls, - response_headers=None, - ) + log.debug("Sending HTTP Request: %s %s", request.method, request.url) - log.debug("Raising connection error") - raise APIConnectionError(request=request) from err + 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( - 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase - ) + 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 - 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 retries > 0 and self._should_retry(err.response): - await err.response.aclose() - return await self._retry_request( - input_options, - cast_to, - retries, - 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: + await err.response.aread() - # 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 - 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, response=response, stream=stream, stream_cls=stream_cls, + retries_taken=retries_taken, ) - async def _retry_request( - self, - options: FinalRequestOptions, - cast_to: Type[ResponseT], - remaining_retries: int, - response_headers: httpx.Headers | None, - *, - stream: bool, - stream_cls: type[_AsyncStreamT] | None, - ) -> ResponseT | _AsyncStreamT: - remaining = remaining_retries - 1 - if remaining == 1: + 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) + log.debug("%i retries left", remaining_retries) - timeout = self._calculate_retry_timeout(remaining, 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, - remaining_retries=remaining, - stream=stream, - stream_cls=stream_cls, - ) - async def _process_response( self, *, @@ -1649,10 +1684,18 @@ async def _process_response( response: httpx.Response, stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, ) -> 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}") @@ -1666,6 +1709,7 @@ async def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ), ) @@ -1679,6 +1723,7 @@ async def _process_response( stream=stream, stream_cls=stream_cls, options=options, + retries_taken=retries_taken, ) if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): return cast(ResponseT, api_response) @@ -1701,8 +1746,7 @@ async def get( cast_to: Type[ResponseT], options: RequestOptions = {}, stream: Literal[False] = False, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def get( @@ -1713,8 +1757,7 @@ async def get( options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def get( @@ -1725,8 +1768,7 @@ async def get( options: RequestOptions = {}, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def get( self, @@ -1747,11 +1789,11 @@ 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, - ) -> ResponseT: - ... + ) -> ResponseT: ... @overload async def post( @@ -1760,12 +1802,12 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], stream_cls: type[_AsyncStreamT], - ) -> _AsyncStreamT: - ... + ) -> _AsyncStreamT: ... @overload async def post( @@ -1774,12 +1816,12 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, stream_cls: type[_AsyncStreamT] | None = None, - ) -> ResponseT | _AsyncStreamT: - ... + ) -> ResponseT | _AsyncStreamT: ... async def post( self, @@ -1787,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) @@ -1803,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( @@ -1814,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) @@ -1828,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( @@ -1854,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 = {} @@ -1995,7 +2091,6 @@ def get_python_version() -> str: def get_architecture() -> Arch: try: - python_bitness, _ = platform.architecture() machine = platform.machine().lower() except Exception: return "unknown" @@ -2011,7 +2106,7 @@ def get_architecture() -> Arch: return "x64" # TODO: untested - if python_bitness == "32bit": + if sys.maxsize <= 2**32: return "x32" if machine: diff --git a/src/onebusaway/_client.py b/src/onebusaway/_client.py index 4d0abe99..5923bba3 100644 --- a/src/onebusaway/_client.py +++ b/src/onebusaway/_client.py @@ -3,26 +3,24 @@ 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 -from . import resources, _exceptions +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 ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, OnebusawaySDKError @@ -32,12 +30,71 @@ AsyncAPIClient, ) +if TYPE_CHECKING: + from .resources import ( + stop, + trip, + block, + route, + shape, + agency, + config, + current_time, + trip_details, + search_for_stop, + stops_for_route, + trips_for_route, + search_for_route, + stops_for_agency, + trip_for_vehicle, + routes_for_agency, + schedule_for_stop, + schedule_for_route, + stops_for_location, + trips_for_location, + routes_for_location, + stop_ids_for_agency, + vehicles_for_agency, + route_ids_for_agency, + arrival_and_departure, + agencies_with_coverage, + report_problem_with_stop, + report_problem_with_trip, + ) + from .resources.stop import StopResource, AsyncStopResource + from .resources.trip import TripResource, AsyncTripResource + from .resources.block import BlockResource, AsyncBlockResource + from .resources.route import RouteResource, AsyncRouteResource + from .resources.shape import ShapeResource, AsyncShapeResource + from .resources.agency import AgencyResource, AsyncAgencyResource + from .resources.config import ConfigResource, AsyncConfigResource + from .resources.current_time import CurrentTimeResource, AsyncCurrentTimeResource + from .resources.trip_details import TripDetailsResource, AsyncTripDetailsResource + from .resources.search_for_stop import SearchForStopResource, AsyncSearchForStopResource + from .resources.stops_for_route import StopsForRouteResource, AsyncStopsForRouteResource + from .resources.trips_for_route import TripsForRouteResource, AsyncTripsForRouteResource + from .resources.search_for_route import SearchForRouteResource, AsyncSearchForRouteResource + from .resources.stops_for_agency import StopsForAgencyResource, AsyncStopsForAgencyResource + from .resources.trip_for_vehicle import TripForVehicleResource, AsyncTripForVehicleResource + from .resources.routes_for_agency import RoutesForAgencyResource, AsyncRoutesForAgencyResource + from .resources.schedule_for_stop import ScheduleForStopResource, AsyncScheduleForStopResource + from .resources.schedule_for_route import ScheduleForRouteResource, AsyncScheduleForRouteResource + from .resources.stops_for_location import StopsForLocationResource, AsyncStopsForLocationResource + from .resources.trips_for_location import TripsForLocationResource, AsyncTripsForLocationResource + from .resources.routes_for_location import RoutesForLocationResource, AsyncRoutesForLocationResource + from .resources.stop_ids_for_agency import StopIDsForAgencyResource, AsyncStopIDsForAgencyResource + from .resources.vehicles_for_agency import VehiclesForAgencyResource, AsyncVehiclesForAgencyResource + from .resources.route_ids_for_agency import RouteIDsForAgencyResource, AsyncRouteIDsForAgencyResource + from .resources.arrival_and_departure import ArrivalAndDepartureResource, AsyncArrivalAndDepartureResource + from .resources.agencies_with_coverage import AgenciesWithCoverageResource, AsyncAgenciesWithCoverageResource + from .resources.report_problem_with_stop import ReportProblemWithStopResource, AsyncReportProblemWithStopResource + from .resources.report_problem_with_trip import ReportProblemWithTripResource, AsyncReportProblemWithTripResource + __all__ = [ "Timeout", "Transport", "ProxiesTypes", "RequestOptions", - "resources", "OnebusawaySDK", "AsyncOnebusawaySDK", "Client", @@ -46,25 +103,6 @@ class OnebusawaySDK(SyncAPIClient): - agencies_with_coverage: resources.AgenciesWithCoverageResource - agency: resources.AgencyResource - vehicles_for_agency: resources.VehiclesForAgencyResource - config: resources.ConfigResource - current_time: resources.CurrentTimeResource - stops_for_location: resources.StopsForLocationResource - stops_for_route: resources.StopsForRouteResource - stop: resources.StopResource - stop_ids_for_agency: resources.StopIDsForAgencyResource - schedule_for_stop: resources.ScheduleForStopResource - route: resources.RouteResource - arrival_and_departure: resources.ArrivalAndDepartureResource - trip: resources.TripResource - trips_for_location: resources.TripsForLocationResource - trip_details: resources.TripDetailsResource - trip_for_vehicle: resources.TripForVehicleResource - with_raw_response: OnebusawaySDKWithRawResponse - with_streaming_response: OnebusawaySDKWithStreamedResponse - # client options api_key: str @@ -73,7 +111,7 @@ def __init__( *, api_key: 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, @@ -91,7 +129,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous onebusaway-sdk client instance. + """Construct a new synchronous OnebusawaySDK client instance. This automatically infers the `api_key` argument from the `ONEBUSAWAY_API_KEY` environment variable if it is not provided. """ @@ -119,24 +157,181 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.agencies_with_coverage = resources.AgenciesWithCoverageResource(self) - self.agency = resources.AgencyResource(self) - self.vehicles_for_agency = resources.VehiclesForAgencyResource(self) - self.config = resources.ConfigResource(self) - self.current_time = resources.CurrentTimeResource(self) - self.stops_for_location = resources.StopsForLocationResource(self) - self.stops_for_route = resources.StopsForRouteResource(self) - self.stop = resources.StopResource(self) - self.stop_ids_for_agency = resources.StopIDsForAgencyResource(self) - self.schedule_for_stop = resources.ScheduleForStopResource(self) - self.route = resources.RouteResource(self) - self.arrival_and_departure = resources.ArrivalAndDepartureResource(self) - self.trip = resources.TripResource(self) - self.trips_for_location = resources.TripsForLocationResource(self) - self.trip_details = resources.TripDetailsResource(self) - self.trip_for_vehicle = resources.TripForVehicleResource(self) - self.with_raw_response = OnebusawaySDKWithRawResponse(self) - self.with_streaming_response = OnebusawaySDKWithStreamedResponse(self) + @cached_property + def agencies_with_coverage(self) -> AgenciesWithCoverageResource: + from .resources.agencies_with_coverage import AgenciesWithCoverageResource + + return AgenciesWithCoverageResource(self) + + @cached_property + def agency(self) -> AgencyResource: + from .resources.agency import AgencyResource + + return AgencyResource(self) + + @cached_property + def vehicles_for_agency(self) -> VehiclesForAgencyResource: + from .resources.vehicles_for_agency import VehiclesForAgencyResource + + return VehiclesForAgencyResource(self) + + @cached_property + def config(self) -> ConfigResource: + from .resources.config import ConfigResource + + return ConfigResource(self) + + @cached_property + def current_time(self) -> CurrentTimeResource: + from .resources.current_time import CurrentTimeResource + + return CurrentTimeResource(self) + + @cached_property + def stops_for_location(self) -> StopsForLocationResource: + from .resources.stops_for_location import StopsForLocationResource + + return StopsForLocationResource(self) + + @cached_property + def stops_for_route(self) -> StopsForRouteResource: + from .resources.stops_for_route import StopsForRouteResource + + return StopsForRouteResource(self) + + @cached_property + def stops_for_agency(self) -> StopsForAgencyResource: + from .resources.stops_for_agency import StopsForAgencyResource + + return StopsForAgencyResource(self) + + @cached_property + def stop(self) -> StopResource: + from .resources.stop import StopResource + + return StopResource(self) + + @cached_property + def stop_ids_for_agency(self) -> StopIDsForAgencyResource: + from .resources.stop_ids_for_agency import StopIDsForAgencyResource + + return StopIDsForAgencyResource(self) + + @cached_property + def schedule_for_stop(self) -> ScheduleForStopResource: + from .resources.schedule_for_stop import ScheduleForStopResource + + return ScheduleForStopResource(self) + + @cached_property + def route(self) -> RouteResource: + from .resources.route import RouteResource + + return RouteResource(self) + + @cached_property + def route_ids_for_agency(self) -> RouteIDsForAgencyResource: + from .resources.route_ids_for_agency import RouteIDsForAgencyResource + + return RouteIDsForAgencyResource(self) + + @cached_property + def routes_for_location(self) -> RoutesForLocationResource: + from .resources.routes_for_location import RoutesForLocationResource + + return RoutesForLocationResource(self) + + @cached_property + def routes_for_agency(self) -> RoutesForAgencyResource: + from .resources.routes_for_agency import RoutesForAgencyResource + + return RoutesForAgencyResource(self) + + @cached_property + def schedule_for_route(self) -> ScheduleForRouteResource: + from .resources.schedule_for_route import ScheduleForRouteResource + + return ScheduleForRouteResource(self) + + @cached_property + def arrival_and_departure(self) -> ArrivalAndDepartureResource: + from .resources.arrival_and_departure import ArrivalAndDepartureResource + + return ArrivalAndDepartureResource(self) + + @cached_property + def trip(self) -> TripResource: + from .resources.trip import TripResource + + return TripResource(self) + + @cached_property + def trips_for_location(self) -> TripsForLocationResource: + from .resources.trips_for_location import TripsForLocationResource + + return TripsForLocationResource(self) + + @cached_property + def trip_details(self) -> TripDetailsResource: + from .resources.trip_details import TripDetailsResource + + return TripDetailsResource(self) + + @cached_property + def trip_for_vehicle(self) -> TripForVehicleResource: + from .resources.trip_for_vehicle import TripForVehicleResource + + return TripForVehicleResource(self) + + @cached_property + def trips_for_route(self) -> TripsForRouteResource: + from .resources.trips_for_route import TripsForRouteResource + + return TripsForRouteResource(self) + + @cached_property + def report_problem_with_stop(self) -> ReportProblemWithStopResource: + from .resources.report_problem_with_stop import ReportProblemWithStopResource + + return ReportProblemWithStopResource(self) + + @cached_property + def report_problem_with_trip(self) -> ReportProblemWithTripResource: + from .resources.report_problem_with_trip import ReportProblemWithTripResource + + return ReportProblemWithTripResource(self) + + @cached_property + def search_for_stop(self) -> SearchForStopResource: + from .resources.search_for_stop import SearchForStopResource + + return SearchForStopResource(self) + + @cached_property + def search_for_route(self) -> SearchForRouteResource: + from .resources.search_for_route import SearchForRouteResource + + return SearchForRouteResource(self) + + @cached_property + def block(self) -> BlockResource: + from .resources.block import BlockResource + + return BlockResource(self) + + @cached_property + def shape(self) -> ShapeResource: + from .resources.shape import ShapeResource + + return ShapeResource(self) + + @cached_property + def with_raw_response(self) -> OnebusawaySDKWithRawResponse: + return OnebusawaySDKWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> OnebusawaySDKWithStreamedResponse: + return OnebusawaySDKWithStreamedResponse(self) @property @override @@ -171,9 +366,9 @@ def copy( *, api_key: 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, @@ -217,6 +412,9 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + def _get_api_key_query_param(self) -> str | None: + return self.api_key + @override def _make_status_error( self, @@ -252,25 +450,6 @@ def _make_status_error( class AsyncOnebusawaySDK(AsyncAPIClient): - agencies_with_coverage: resources.AsyncAgenciesWithCoverageResource - agency: resources.AsyncAgencyResource - vehicles_for_agency: resources.AsyncVehiclesForAgencyResource - config: resources.AsyncConfigResource - current_time: resources.AsyncCurrentTimeResource - stops_for_location: resources.AsyncStopsForLocationResource - stops_for_route: resources.AsyncStopsForRouteResource - stop: resources.AsyncStopResource - stop_ids_for_agency: resources.AsyncStopIDsForAgencyResource - schedule_for_stop: resources.AsyncScheduleForStopResource - route: resources.AsyncRouteResource - arrival_and_departure: resources.AsyncArrivalAndDepartureResource - trip: resources.AsyncTripResource - trips_for_location: resources.AsyncTripsForLocationResource - trip_details: resources.AsyncTripDetailsResource - trip_for_vehicle: resources.AsyncTripForVehicleResource - with_raw_response: AsyncOnebusawaySDKWithRawResponse - with_streaming_response: AsyncOnebusawaySDKWithStreamedResponse - # client options api_key: str @@ -279,7 +458,7 @@ def __init__( *, api_key: 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, @@ -297,7 +476,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async onebusaway-sdk client instance. + """Construct a new async AsyncOnebusawaySDK client instance. This automatically infers the `api_key` argument from the `ONEBUSAWAY_API_KEY` environment variable if it is not provided. """ @@ -325,24 +504,181 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.agencies_with_coverage = resources.AsyncAgenciesWithCoverageResource(self) - self.agency = resources.AsyncAgencyResource(self) - self.vehicles_for_agency = resources.AsyncVehiclesForAgencyResource(self) - self.config = resources.AsyncConfigResource(self) - self.current_time = resources.AsyncCurrentTimeResource(self) - self.stops_for_location = resources.AsyncStopsForLocationResource(self) - self.stops_for_route = resources.AsyncStopsForRouteResource(self) - self.stop = resources.AsyncStopResource(self) - self.stop_ids_for_agency = resources.AsyncStopIDsForAgencyResource(self) - self.schedule_for_stop = resources.AsyncScheduleForStopResource(self) - self.route = resources.AsyncRouteResource(self) - self.arrival_and_departure = resources.AsyncArrivalAndDepartureResource(self) - self.trip = resources.AsyncTripResource(self) - self.trips_for_location = resources.AsyncTripsForLocationResource(self) - self.trip_details = resources.AsyncTripDetailsResource(self) - self.trip_for_vehicle = resources.AsyncTripForVehicleResource(self) - self.with_raw_response = AsyncOnebusawaySDKWithRawResponse(self) - self.with_streaming_response = AsyncOnebusawaySDKWithStreamedResponse(self) + @cached_property + def agencies_with_coverage(self) -> AsyncAgenciesWithCoverageResource: + from .resources.agencies_with_coverage import AsyncAgenciesWithCoverageResource + + return AsyncAgenciesWithCoverageResource(self) + + @cached_property + def agency(self) -> AsyncAgencyResource: + from .resources.agency import AsyncAgencyResource + + return AsyncAgencyResource(self) + + @cached_property + def vehicles_for_agency(self) -> AsyncVehiclesForAgencyResource: + from .resources.vehicles_for_agency import AsyncVehiclesForAgencyResource + + return AsyncVehiclesForAgencyResource(self) + + @cached_property + def config(self) -> AsyncConfigResource: + from .resources.config import AsyncConfigResource + + return AsyncConfigResource(self) + + @cached_property + def current_time(self) -> AsyncCurrentTimeResource: + from .resources.current_time import AsyncCurrentTimeResource + + return AsyncCurrentTimeResource(self) + + @cached_property + def stops_for_location(self) -> AsyncStopsForLocationResource: + from .resources.stops_for_location import AsyncStopsForLocationResource + + return AsyncStopsForLocationResource(self) + + @cached_property + def stops_for_route(self) -> AsyncStopsForRouteResource: + from .resources.stops_for_route import AsyncStopsForRouteResource + + return AsyncStopsForRouteResource(self) + + @cached_property + def stops_for_agency(self) -> AsyncStopsForAgencyResource: + from .resources.stops_for_agency import AsyncStopsForAgencyResource + + return AsyncStopsForAgencyResource(self) + + @cached_property + def stop(self) -> AsyncStopResource: + from .resources.stop import AsyncStopResource + + return AsyncStopResource(self) + + @cached_property + def stop_ids_for_agency(self) -> AsyncStopIDsForAgencyResource: + from .resources.stop_ids_for_agency import AsyncStopIDsForAgencyResource + + return AsyncStopIDsForAgencyResource(self) + + @cached_property + def schedule_for_stop(self) -> AsyncScheduleForStopResource: + from .resources.schedule_for_stop import AsyncScheduleForStopResource + + return AsyncScheduleForStopResource(self) + + @cached_property + def route(self) -> AsyncRouteResource: + from .resources.route import AsyncRouteResource + + return AsyncRouteResource(self) + + @cached_property + def route_ids_for_agency(self) -> AsyncRouteIDsForAgencyResource: + from .resources.route_ids_for_agency import AsyncRouteIDsForAgencyResource + + return AsyncRouteIDsForAgencyResource(self) + + @cached_property + def routes_for_location(self) -> AsyncRoutesForLocationResource: + from .resources.routes_for_location import AsyncRoutesForLocationResource + + return AsyncRoutesForLocationResource(self) + + @cached_property + def routes_for_agency(self) -> AsyncRoutesForAgencyResource: + from .resources.routes_for_agency import AsyncRoutesForAgencyResource + + return AsyncRoutesForAgencyResource(self) + + @cached_property + def schedule_for_route(self) -> AsyncScheduleForRouteResource: + from .resources.schedule_for_route import AsyncScheduleForRouteResource + + return AsyncScheduleForRouteResource(self) + + @cached_property + def arrival_and_departure(self) -> AsyncArrivalAndDepartureResource: + from .resources.arrival_and_departure import AsyncArrivalAndDepartureResource + + return AsyncArrivalAndDepartureResource(self) + + @cached_property + def trip(self) -> AsyncTripResource: + from .resources.trip import AsyncTripResource + + return AsyncTripResource(self) + + @cached_property + def trips_for_location(self) -> AsyncTripsForLocationResource: + from .resources.trips_for_location import AsyncTripsForLocationResource + + return AsyncTripsForLocationResource(self) + + @cached_property + def trip_details(self) -> AsyncTripDetailsResource: + from .resources.trip_details import AsyncTripDetailsResource + + return AsyncTripDetailsResource(self) + + @cached_property + def trip_for_vehicle(self) -> AsyncTripForVehicleResource: + from .resources.trip_for_vehicle import AsyncTripForVehicleResource + + return AsyncTripForVehicleResource(self) + + @cached_property + def trips_for_route(self) -> AsyncTripsForRouteResource: + from .resources.trips_for_route import AsyncTripsForRouteResource + + return AsyncTripsForRouteResource(self) + + @cached_property + def report_problem_with_stop(self) -> AsyncReportProblemWithStopResource: + from .resources.report_problem_with_stop import AsyncReportProblemWithStopResource + + return AsyncReportProblemWithStopResource(self) + + @cached_property + def report_problem_with_trip(self) -> AsyncReportProblemWithTripResource: + from .resources.report_problem_with_trip import AsyncReportProblemWithTripResource + + return AsyncReportProblemWithTripResource(self) + + @cached_property + def search_for_stop(self) -> AsyncSearchForStopResource: + from .resources.search_for_stop import AsyncSearchForStopResource + + return AsyncSearchForStopResource(self) + + @cached_property + def search_for_route(self) -> AsyncSearchForRouteResource: + from .resources.search_for_route import AsyncSearchForRouteResource + + return AsyncSearchForRouteResource(self) + + @cached_property + def block(self) -> AsyncBlockResource: + from .resources.block import AsyncBlockResource + + return AsyncBlockResource(self) + + @cached_property + def shape(self) -> AsyncShapeResource: + from .resources.shape import AsyncShapeResource + + return AsyncShapeResource(self) + + @cached_property + def with_raw_response(self) -> AsyncOnebusawaySDKWithRawResponse: + return AsyncOnebusawaySDKWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncOnebusawaySDKWithStreamedResponse: + return AsyncOnebusawaySDKWithStreamedResponse(self) @property @override @@ -377,9 +713,9 @@ def copy( *, api_key: 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, @@ -423,6 +759,9 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + def _get_api_key_query_param(self) -> str | None: + return self.api_key + @override def _make_status_error( self, @@ -458,105 +797,707 @@ def _make_status_error( class OnebusawaySDKWithRawResponse: + _client: OnebusawaySDK + def __init__(self, client: OnebusawaySDK) -> None: - self.agencies_with_coverage = resources.AgenciesWithCoverageResourceWithRawResponse( - client.agencies_with_coverage - ) - self.agency = resources.AgencyResourceWithRawResponse(client.agency) - self.vehicles_for_agency = resources.VehiclesForAgencyResourceWithRawResponse(client.vehicles_for_agency) - self.config = resources.ConfigResourceWithRawResponse(client.config) - self.current_time = resources.CurrentTimeResourceWithRawResponse(client.current_time) - self.stops_for_location = resources.StopsForLocationResourceWithRawResponse(client.stops_for_location) - self.stops_for_route = resources.StopsForRouteResourceWithRawResponse(client.stops_for_route) - self.stop = resources.StopResourceWithRawResponse(client.stop) - self.stop_ids_for_agency = resources.StopIDsForAgencyResourceWithRawResponse(client.stop_ids_for_agency) - self.schedule_for_stop = resources.ScheduleForStopResourceWithRawResponse(client.schedule_for_stop) - self.route = resources.RouteResourceWithRawResponse(client.route) - self.arrival_and_departure = resources.ArrivalAndDepartureResourceWithRawResponse(client.arrival_and_departure) - self.trip = resources.TripResourceWithRawResponse(client.trip) - self.trips_for_location = resources.TripsForLocationResourceWithRawResponse(client.trips_for_location) - self.trip_details = resources.TripDetailsResourceWithRawResponse(client.trip_details) - self.trip_for_vehicle = resources.TripForVehicleResourceWithRawResponse(client.trip_for_vehicle) + self._client = client + + @cached_property + def agencies_with_coverage(self) -> agencies_with_coverage.AgenciesWithCoverageResourceWithRawResponse: + from .resources.agencies_with_coverage import AgenciesWithCoverageResourceWithRawResponse + + return AgenciesWithCoverageResourceWithRawResponse(self._client.agencies_with_coverage) + + @cached_property + def agency(self) -> agency.AgencyResourceWithRawResponse: + from .resources.agency import AgencyResourceWithRawResponse + + return AgencyResourceWithRawResponse(self._client.agency) + + @cached_property + def vehicles_for_agency(self) -> vehicles_for_agency.VehiclesForAgencyResourceWithRawResponse: + from .resources.vehicles_for_agency import VehiclesForAgencyResourceWithRawResponse + + return VehiclesForAgencyResourceWithRawResponse(self._client.vehicles_for_agency) + + @cached_property + def config(self) -> config.ConfigResourceWithRawResponse: + from .resources.config import ConfigResourceWithRawResponse + + return ConfigResourceWithRawResponse(self._client.config) + + @cached_property + def current_time(self) -> current_time.CurrentTimeResourceWithRawResponse: + from .resources.current_time import CurrentTimeResourceWithRawResponse + + return CurrentTimeResourceWithRawResponse(self._client.current_time) + + @cached_property + def stops_for_location(self) -> stops_for_location.StopsForLocationResourceWithRawResponse: + from .resources.stops_for_location import StopsForLocationResourceWithRawResponse + + return StopsForLocationResourceWithRawResponse(self._client.stops_for_location) + + @cached_property + def stops_for_route(self) -> stops_for_route.StopsForRouteResourceWithRawResponse: + from .resources.stops_for_route import StopsForRouteResourceWithRawResponse + + return StopsForRouteResourceWithRawResponse(self._client.stops_for_route) + + @cached_property + def stops_for_agency(self) -> stops_for_agency.StopsForAgencyResourceWithRawResponse: + from .resources.stops_for_agency import StopsForAgencyResourceWithRawResponse + + return StopsForAgencyResourceWithRawResponse(self._client.stops_for_agency) + + @cached_property + def stop(self) -> stop.StopResourceWithRawResponse: + from .resources.stop import StopResourceWithRawResponse + + return StopResourceWithRawResponse(self._client.stop) + + @cached_property + def stop_ids_for_agency(self) -> stop_ids_for_agency.StopIDsForAgencyResourceWithRawResponse: + from .resources.stop_ids_for_agency import StopIDsForAgencyResourceWithRawResponse + + return StopIDsForAgencyResourceWithRawResponse(self._client.stop_ids_for_agency) + + @cached_property + def schedule_for_stop(self) -> schedule_for_stop.ScheduleForStopResourceWithRawResponse: + from .resources.schedule_for_stop import ScheduleForStopResourceWithRawResponse + + return ScheduleForStopResourceWithRawResponse(self._client.schedule_for_stop) + + @cached_property + def route(self) -> route.RouteResourceWithRawResponse: + from .resources.route import RouteResourceWithRawResponse + + return RouteResourceWithRawResponse(self._client.route) + + @cached_property + def route_ids_for_agency(self) -> route_ids_for_agency.RouteIDsForAgencyResourceWithRawResponse: + from .resources.route_ids_for_agency import RouteIDsForAgencyResourceWithRawResponse + + return RouteIDsForAgencyResourceWithRawResponse(self._client.route_ids_for_agency) + + @cached_property + def routes_for_location(self) -> routes_for_location.RoutesForLocationResourceWithRawResponse: + from .resources.routes_for_location import RoutesForLocationResourceWithRawResponse + + return RoutesForLocationResourceWithRawResponse(self._client.routes_for_location) + + @cached_property + def routes_for_agency(self) -> routes_for_agency.RoutesForAgencyResourceWithRawResponse: + from .resources.routes_for_agency import RoutesForAgencyResourceWithRawResponse + + return RoutesForAgencyResourceWithRawResponse(self._client.routes_for_agency) + + @cached_property + def schedule_for_route(self) -> schedule_for_route.ScheduleForRouteResourceWithRawResponse: + from .resources.schedule_for_route import ScheduleForRouteResourceWithRawResponse + + return ScheduleForRouteResourceWithRawResponse(self._client.schedule_for_route) + + @cached_property + def arrival_and_departure(self) -> arrival_and_departure.ArrivalAndDepartureResourceWithRawResponse: + from .resources.arrival_and_departure import ArrivalAndDepartureResourceWithRawResponse + + return ArrivalAndDepartureResourceWithRawResponse(self._client.arrival_and_departure) + + @cached_property + def trip(self) -> trip.TripResourceWithRawResponse: + from .resources.trip import TripResourceWithRawResponse + + return TripResourceWithRawResponse(self._client.trip) + + @cached_property + def trips_for_location(self) -> trips_for_location.TripsForLocationResourceWithRawResponse: + from .resources.trips_for_location import TripsForLocationResourceWithRawResponse + + return TripsForLocationResourceWithRawResponse(self._client.trips_for_location) + + @cached_property + def trip_details(self) -> trip_details.TripDetailsResourceWithRawResponse: + from .resources.trip_details import TripDetailsResourceWithRawResponse + + return TripDetailsResourceWithRawResponse(self._client.trip_details) + + @cached_property + def trip_for_vehicle(self) -> trip_for_vehicle.TripForVehicleResourceWithRawResponse: + from .resources.trip_for_vehicle import TripForVehicleResourceWithRawResponse + + return TripForVehicleResourceWithRawResponse(self._client.trip_for_vehicle) + + @cached_property + def trips_for_route(self) -> trips_for_route.TripsForRouteResourceWithRawResponse: + from .resources.trips_for_route import TripsForRouteResourceWithRawResponse + + return TripsForRouteResourceWithRawResponse(self._client.trips_for_route) + + @cached_property + def report_problem_with_stop(self) -> report_problem_with_stop.ReportProblemWithStopResourceWithRawResponse: + from .resources.report_problem_with_stop import ReportProblemWithStopResourceWithRawResponse + + return ReportProblemWithStopResourceWithRawResponse(self._client.report_problem_with_stop) + + @cached_property + def report_problem_with_trip(self) -> report_problem_with_trip.ReportProblemWithTripResourceWithRawResponse: + from .resources.report_problem_with_trip import ReportProblemWithTripResourceWithRawResponse + + return ReportProblemWithTripResourceWithRawResponse(self._client.report_problem_with_trip) + + @cached_property + def search_for_stop(self) -> search_for_stop.SearchForStopResourceWithRawResponse: + from .resources.search_for_stop import SearchForStopResourceWithRawResponse + + return SearchForStopResourceWithRawResponse(self._client.search_for_stop) + + @cached_property + def search_for_route(self) -> search_for_route.SearchForRouteResourceWithRawResponse: + from .resources.search_for_route import SearchForRouteResourceWithRawResponse + + return SearchForRouteResourceWithRawResponse(self._client.search_for_route) + + @cached_property + def block(self) -> block.BlockResourceWithRawResponse: + from .resources.block import BlockResourceWithRawResponse + + return BlockResourceWithRawResponse(self._client.block) + + @cached_property + def shape(self) -> shape.ShapeResourceWithRawResponse: + from .resources.shape import ShapeResourceWithRawResponse + + return ShapeResourceWithRawResponse(self._client.shape) class AsyncOnebusawaySDKWithRawResponse: + _client: AsyncOnebusawaySDK + def __init__(self, client: AsyncOnebusawaySDK) -> None: - self.agencies_with_coverage = resources.AsyncAgenciesWithCoverageResourceWithRawResponse( - client.agencies_with_coverage - ) - self.agency = resources.AsyncAgencyResourceWithRawResponse(client.agency) - self.vehicles_for_agency = resources.AsyncVehiclesForAgencyResourceWithRawResponse(client.vehicles_for_agency) - self.config = resources.AsyncConfigResourceWithRawResponse(client.config) - self.current_time = resources.AsyncCurrentTimeResourceWithRawResponse(client.current_time) - self.stops_for_location = resources.AsyncStopsForLocationResourceWithRawResponse(client.stops_for_location) - self.stops_for_route = resources.AsyncStopsForRouteResourceWithRawResponse(client.stops_for_route) - self.stop = resources.AsyncStopResourceWithRawResponse(client.stop) - self.stop_ids_for_agency = resources.AsyncStopIDsForAgencyResourceWithRawResponse(client.stop_ids_for_agency) - self.schedule_for_stop = resources.AsyncScheduleForStopResourceWithRawResponse(client.schedule_for_stop) - self.route = resources.AsyncRouteResourceWithRawResponse(client.route) - self.arrival_and_departure = resources.AsyncArrivalAndDepartureResourceWithRawResponse( - client.arrival_and_departure - ) - self.trip = resources.AsyncTripResourceWithRawResponse(client.trip) - self.trips_for_location = resources.AsyncTripsForLocationResourceWithRawResponse(client.trips_for_location) - self.trip_details = resources.AsyncTripDetailsResourceWithRawResponse(client.trip_details) - self.trip_for_vehicle = resources.AsyncTripForVehicleResourceWithRawResponse(client.trip_for_vehicle) + self._client = client + + @cached_property + def agencies_with_coverage(self) -> agencies_with_coverage.AsyncAgenciesWithCoverageResourceWithRawResponse: + from .resources.agencies_with_coverage import AsyncAgenciesWithCoverageResourceWithRawResponse + + return AsyncAgenciesWithCoverageResourceWithRawResponse(self._client.agencies_with_coverage) + + @cached_property + def agency(self) -> agency.AsyncAgencyResourceWithRawResponse: + from .resources.agency import AsyncAgencyResourceWithRawResponse + + return AsyncAgencyResourceWithRawResponse(self._client.agency) + + @cached_property + def vehicles_for_agency(self) -> vehicles_for_agency.AsyncVehiclesForAgencyResourceWithRawResponse: + from .resources.vehicles_for_agency import AsyncVehiclesForAgencyResourceWithRawResponse + + return AsyncVehiclesForAgencyResourceWithRawResponse(self._client.vehicles_for_agency) + + @cached_property + def config(self) -> config.AsyncConfigResourceWithRawResponse: + from .resources.config import AsyncConfigResourceWithRawResponse + + return AsyncConfigResourceWithRawResponse(self._client.config) + + @cached_property + def current_time(self) -> current_time.AsyncCurrentTimeResourceWithRawResponse: + from .resources.current_time import AsyncCurrentTimeResourceWithRawResponse + + return AsyncCurrentTimeResourceWithRawResponse(self._client.current_time) + + @cached_property + def stops_for_location(self) -> stops_for_location.AsyncStopsForLocationResourceWithRawResponse: + from .resources.stops_for_location import AsyncStopsForLocationResourceWithRawResponse + + return AsyncStopsForLocationResourceWithRawResponse(self._client.stops_for_location) + + @cached_property + def stops_for_route(self) -> stops_for_route.AsyncStopsForRouteResourceWithRawResponse: + from .resources.stops_for_route import AsyncStopsForRouteResourceWithRawResponse + + return AsyncStopsForRouteResourceWithRawResponse(self._client.stops_for_route) + + @cached_property + def stops_for_agency(self) -> stops_for_agency.AsyncStopsForAgencyResourceWithRawResponse: + from .resources.stops_for_agency import AsyncStopsForAgencyResourceWithRawResponse + + return AsyncStopsForAgencyResourceWithRawResponse(self._client.stops_for_agency) + + @cached_property + def stop(self) -> stop.AsyncStopResourceWithRawResponse: + from .resources.stop import AsyncStopResourceWithRawResponse + + return AsyncStopResourceWithRawResponse(self._client.stop) + + @cached_property + def stop_ids_for_agency(self) -> stop_ids_for_agency.AsyncStopIDsForAgencyResourceWithRawResponse: + from .resources.stop_ids_for_agency import AsyncStopIDsForAgencyResourceWithRawResponse + + return AsyncStopIDsForAgencyResourceWithRawResponse(self._client.stop_ids_for_agency) + + @cached_property + def schedule_for_stop(self) -> schedule_for_stop.AsyncScheduleForStopResourceWithRawResponse: + from .resources.schedule_for_stop import AsyncScheduleForStopResourceWithRawResponse + + return AsyncScheduleForStopResourceWithRawResponse(self._client.schedule_for_stop) + + @cached_property + def route(self) -> route.AsyncRouteResourceWithRawResponse: + from .resources.route import AsyncRouteResourceWithRawResponse + + return AsyncRouteResourceWithRawResponse(self._client.route) + + @cached_property + def route_ids_for_agency(self) -> route_ids_for_agency.AsyncRouteIDsForAgencyResourceWithRawResponse: + from .resources.route_ids_for_agency import AsyncRouteIDsForAgencyResourceWithRawResponse + + return AsyncRouteIDsForAgencyResourceWithRawResponse(self._client.route_ids_for_agency) + + @cached_property + def routes_for_location(self) -> routes_for_location.AsyncRoutesForLocationResourceWithRawResponse: + from .resources.routes_for_location import AsyncRoutesForLocationResourceWithRawResponse + + return AsyncRoutesForLocationResourceWithRawResponse(self._client.routes_for_location) + + @cached_property + def routes_for_agency(self) -> routes_for_agency.AsyncRoutesForAgencyResourceWithRawResponse: + from .resources.routes_for_agency import AsyncRoutesForAgencyResourceWithRawResponse + + return AsyncRoutesForAgencyResourceWithRawResponse(self._client.routes_for_agency) + + @cached_property + def schedule_for_route(self) -> schedule_for_route.AsyncScheduleForRouteResourceWithRawResponse: + from .resources.schedule_for_route import AsyncScheduleForRouteResourceWithRawResponse + + return AsyncScheduleForRouteResourceWithRawResponse(self._client.schedule_for_route) + + @cached_property + def arrival_and_departure(self) -> arrival_and_departure.AsyncArrivalAndDepartureResourceWithRawResponse: + from .resources.arrival_and_departure import AsyncArrivalAndDepartureResourceWithRawResponse + + return AsyncArrivalAndDepartureResourceWithRawResponse(self._client.arrival_and_departure) + + @cached_property + def trip(self) -> trip.AsyncTripResourceWithRawResponse: + from .resources.trip import AsyncTripResourceWithRawResponse + + return AsyncTripResourceWithRawResponse(self._client.trip) + + @cached_property + def trips_for_location(self) -> trips_for_location.AsyncTripsForLocationResourceWithRawResponse: + from .resources.trips_for_location import AsyncTripsForLocationResourceWithRawResponse + + return AsyncTripsForLocationResourceWithRawResponse(self._client.trips_for_location) + + @cached_property + def trip_details(self) -> trip_details.AsyncTripDetailsResourceWithRawResponse: + from .resources.trip_details import AsyncTripDetailsResourceWithRawResponse + + return AsyncTripDetailsResourceWithRawResponse(self._client.trip_details) + + @cached_property + def trip_for_vehicle(self) -> trip_for_vehicle.AsyncTripForVehicleResourceWithRawResponse: + from .resources.trip_for_vehicle import AsyncTripForVehicleResourceWithRawResponse + + return AsyncTripForVehicleResourceWithRawResponse(self._client.trip_for_vehicle) + + @cached_property + def trips_for_route(self) -> trips_for_route.AsyncTripsForRouteResourceWithRawResponse: + from .resources.trips_for_route import AsyncTripsForRouteResourceWithRawResponse + + return AsyncTripsForRouteResourceWithRawResponse(self._client.trips_for_route) + + @cached_property + def report_problem_with_stop(self) -> report_problem_with_stop.AsyncReportProblemWithStopResourceWithRawResponse: + from .resources.report_problem_with_stop import AsyncReportProblemWithStopResourceWithRawResponse + + return AsyncReportProblemWithStopResourceWithRawResponse(self._client.report_problem_with_stop) + + @cached_property + def report_problem_with_trip(self) -> report_problem_with_trip.AsyncReportProblemWithTripResourceWithRawResponse: + from .resources.report_problem_with_trip import AsyncReportProblemWithTripResourceWithRawResponse + + return AsyncReportProblemWithTripResourceWithRawResponse(self._client.report_problem_with_trip) + + @cached_property + def search_for_stop(self) -> search_for_stop.AsyncSearchForStopResourceWithRawResponse: + from .resources.search_for_stop import AsyncSearchForStopResourceWithRawResponse + + return AsyncSearchForStopResourceWithRawResponse(self._client.search_for_stop) + + @cached_property + def search_for_route(self) -> search_for_route.AsyncSearchForRouteResourceWithRawResponse: + from .resources.search_for_route import AsyncSearchForRouteResourceWithRawResponse + + return AsyncSearchForRouteResourceWithRawResponse(self._client.search_for_route) + + @cached_property + def block(self) -> block.AsyncBlockResourceWithRawResponse: + from .resources.block import AsyncBlockResourceWithRawResponse + + return AsyncBlockResourceWithRawResponse(self._client.block) + + @cached_property + def shape(self) -> shape.AsyncShapeResourceWithRawResponse: + from .resources.shape import AsyncShapeResourceWithRawResponse + + return AsyncShapeResourceWithRawResponse(self._client.shape) class OnebusawaySDKWithStreamedResponse: + _client: OnebusawaySDK + def __init__(self, client: OnebusawaySDK) -> None: - self.agencies_with_coverage = resources.AgenciesWithCoverageResourceWithStreamingResponse( - client.agencies_with_coverage - ) - self.agency = resources.AgencyResourceWithStreamingResponse(client.agency) - self.vehicles_for_agency = resources.VehiclesForAgencyResourceWithStreamingResponse(client.vehicles_for_agency) - self.config = resources.ConfigResourceWithStreamingResponse(client.config) - self.current_time = resources.CurrentTimeResourceWithStreamingResponse(client.current_time) - self.stops_for_location = resources.StopsForLocationResourceWithStreamingResponse(client.stops_for_location) - self.stops_for_route = resources.StopsForRouteResourceWithStreamingResponse(client.stops_for_route) - self.stop = resources.StopResourceWithStreamingResponse(client.stop) - self.stop_ids_for_agency = resources.StopIDsForAgencyResourceWithStreamingResponse(client.stop_ids_for_agency) - self.schedule_for_stop = resources.ScheduleForStopResourceWithStreamingResponse(client.schedule_for_stop) - self.route = resources.RouteResourceWithStreamingResponse(client.route) - self.arrival_and_departure = resources.ArrivalAndDepartureResourceWithStreamingResponse( - client.arrival_and_departure - ) - self.trip = resources.TripResourceWithStreamingResponse(client.trip) - self.trips_for_location = resources.TripsForLocationResourceWithStreamingResponse(client.trips_for_location) - self.trip_details = resources.TripDetailsResourceWithStreamingResponse(client.trip_details) - self.trip_for_vehicle = resources.TripForVehicleResourceWithStreamingResponse(client.trip_for_vehicle) + self._client = client + + @cached_property + def agencies_with_coverage(self) -> agencies_with_coverage.AgenciesWithCoverageResourceWithStreamingResponse: + from .resources.agencies_with_coverage import AgenciesWithCoverageResourceWithStreamingResponse + + return AgenciesWithCoverageResourceWithStreamingResponse(self._client.agencies_with_coverage) + + @cached_property + def agency(self) -> agency.AgencyResourceWithStreamingResponse: + from .resources.agency import AgencyResourceWithStreamingResponse + + return AgencyResourceWithStreamingResponse(self._client.agency) + + @cached_property + def vehicles_for_agency(self) -> vehicles_for_agency.VehiclesForAgencyResourceWithStreamingResponse: + from .resources.vehicles_for_agency import VehiclesForAgencyResourceWithStreamingResponse + + return VehiclesForAgencyResourceWithStreamingResponse(self._client.vehicles_for_agency) + + @cached_property + def config(self) -> config.ConfigResourceWithStreamingResponse: + from .resources.config import ConfigResourceWithStreamingResponse + + return ConfigResourceWithStreamingResponse(self._client.config) + + @cached_property + def current_time(self) -> current_time.CurrentTimeResourceWithStreamingResponse: + from .resources.current_time import CurrentTimeResourceWithStreamingResponse + + return CurrentTimeResourceWithStreamingResponse(self._client.current_time) + + @cached_property + def stops_for_location(self) -> stops_for_location.StopsForLocationResourceWithStreamingResponse: + from .resources.stops_for_location import StopsForLocationResourceWithStreamingResponse + + return StopsForLocationResourceWithStreamingResponse(self._client.stops_for_location) + + @cached_property + def stops_for_route(self) -> stops_for_route.StopsForRouteResourceWithStreamingResponse: + from .resources.stops_for_route import StopsForRouteResourceWithStreamingResponse + + return StopsForRouteResourceWithStreamingResponse(self._client.stops_for_route) + + @cached_property + def stops_for_agency(self) -> stops_for_agency.StopsForAgencyResourceWithStreamingResponse: + from .resources.stops_for_agency import StopsForAgencyResourceWithStreamingResponse + + return StopsForAgencyResourceWithStreamingResponse(self._client.stops_for_agency) + + @cached_property + def stop(self) -> stop.StopResourceWithStreamingResponse: + from .resources.stop import StopResourceWithStreamingResponse + + return StopResourceWithStreamingResponse(self._client.stop) + + @cached_property + def stop_ids_for_agency(self) -> stop_ids_for_agency.StopIDsForAgencyResourceWithStreamingResponse: + from .resources.stop_ids_for_agency import StopIDsForAgencyResourceWithStreamingResponse + + return StopIDsForAgencyResourceWithStreamingResponse(self._client.stop_ids_for_agency) + + @cached_property + def schedule_for_stop(self) -> schedule_for_stop.ScheduleForStopResourceWithStreamingResponse: + from .resources.schedule_for_stop import ScheduleForStopResourceWithStreamingResponse + + return ScheduleForStopResourceWithStreamingResponse(self._client.schedule_for_stop) + + @cached_property + def route(self) -> route.RouteResourceWithStreamingResponse: + from .resources.route import RouteResourceWithStreamingResponse + + return RouteResourceWithStreamingResponse(self._client.route) + + @cached_property + def route_ids_for_agency(self) -> route_ids_for_agency.RouteIDsForAgencyResourceWithStreamingResponse: + from .resources.route_ids_for_agency import RouteIDsForAgencyResourceWithStreamingResponse + + return RouteIDsForAgencyResourceWithStreamingResponse(self._client.route_ids_for_agency) + + @cached_property + def routes_for_location(self) -> routes_for_location.RoutesForLocationResourceWithStreamingResponse: + from .resources.routes_for_location import RoutesForLocationResourceWithStreamingResponse + + return RoutesForLocationResourceWithStreamingResponse(self._client.routes_for_location) + + @cached_property + def routes_for_agency(self) -> routes_for_agency.RoutesForAgencyResourceWithStreamingResponse: + from .resources.routes_for_agency import RoutesForAgencyResourceWithStreamingResponse + + return RoutesForAgencyResourceWithStreamingResponse(self._client.routes_for_agency) + + @cached_property + def schedule_for_route(self) -> schedule_for_route.ScheduleForRouteResourceWithStreamingResponse: + from .resources.schedule_for_route import ScheduleForRouteResourceWithStreamingResponse + + return ScheduleForRouteResourceWithStreamingResponse(self._client.schedule_for_route) + + @cached_property + def arrival_and_departure(self) -> arrival_and_departure.ArrivalAndDepartureResourceWithStreamingResponse: + from .resources.arrival_and_departure import ArrivalAndDepartureResourceWithStreamingResponse + + return ArrivalAndDepartureResourceWithStreamingResponse(self._client.arrival_and_departure) + + @cached_property + def trip(self) -> trip.TripResourceWithStreamingResponse: + from .resources.trip import TripResourceWithStreamingResponse + + return TripResourceWithStreamingResponse(self._client.trip) + + @cached_property + def trips_for_location(self) -> trips_for_location.TripsForLocationResourceWithStreamingResponse: + from .resources.trips_for_location import TripsForLocationResourceWithStreamingResponse + + return TripsForLocationResourceWithStreamingResponse(self._client.trips_for_location) + + @cached_property + def trip_details(self) -> trip_details.TripDetailsResourceWithStreamingResponse: + from .resources.trip_details import TripDetailsResourceWithStreamingResponse + + return TripDetailsResourceWithStreamingResponse(self._client.trip_details) + + @cached_property + def trip_for_vehicle(self) -> trip_for_vehicle.TripForVehicleResourceWithStreamingResponse: + from .resources.trip_for_vehicle import TripForVehicleResourceWithStreamingResponse + + return TripForVehicleResourceWithStreamingResponse(self._client.trip_for_vehicle) + + @cached_property + def trips_for_route(self) -> trips_for_route.TripsForRouteResourceWithStreamingResponse: + from .resources.trips_for_route import TripsForRouteResourceWithStreamingResponse + + return TripsForRouteResourceWithStreamingResponse(self._client.trips_for_route) + + @cached_property + def report_problem_with_stop(self) -> report_problem_with_stop.ReportProblemWithStopResourceWithStreamingResponse: + from .resources.report_problem_with_stop import ReportProblemWithStopResourceWithStreamingResponse + + return ReportProblemWithStopResourceWithStreamingResponse(self._client.report_problem_with_stop) + + @cached_property + def report_problem_with_trip(self) -> report_problem_with_trip.ReportProblemWithTripResourceWithStreamingResponse: + from .resources.report_problem_with_trip import ReportProblemWithTripResourceWithStreamingResponse + + return ReportProblemWithTripResourceWithStreamingResponse(self._client.report_problem_with_trip) + + @cached_property + def search_for_stop(self) -> search_for_stop.SearchForStopResourceWithStreamingResponse: + from .resources.search_for_stop import SearchForStopResourceWithStreamingResponse + + return SearchForStopResourceWithStreamingResponse(self._client.search_for_stop) + + @cached_property + def search_for_route(self) -> search_for_route.SearchForRouteResourceWithStreamingResponse: + from .resources.search_for_route import SearchForRouteResourceWithStreamingResponse + + return SearchForRouteResourceWithStreamingResponse(self._client.search_for_route) + + @cached_property + def block(self) -> block.BlockResourceWithStreamingResponse: + from .resources.block import BlockResourceWithStreamingResponse + + return BlockResourceWithStreamingResponse(self._client.block) + + @cached_property + def shape(self) -> shape.ShapeResourceWithStreamingResponse: + from .resources.shape import ShapeResourceWithStreamingResponse + + return ShapeResourceWithStreamingResponse(self._client.shape) class AsyncOnebusawaySDKWithStreamedResponse: + _client: AsyncOnebusawaySDK + def __init__(self, client: AsyncOnebusawaySDK) -> None: - self.agencies_with_coverage = resources.AsyncAgenciesWithCoverageResourceWithStreamingResponse( - client.agencies_with_coverage - ) - self.agency = resources.AsyncAgencyResourceWithStreamingResponse(client.agency) - self.vehicles_for_agency = resources.AsyncVehiclesForAgencyResourceWithStreamingResponse( - client.vehicles_for_agency - ) - self.config = resources.AsyncConfigResourceWithStreamingResponse(client.config) - self.current_time = resources.AsyncCurrentTimeResourceWithStreamingResponse(client.current_time) - self.stops_for_location = resources.AsyncStopsForLocationResourceWithStreamingResponse( - client.stops_for_location - ) - self.stops_for_route = resources.AsyncStopsForRouteResourceWithStreamingResponse(client.stops_for_route) - self.stop = resources.AsyncStopResourceWithStreamingResponse(client.stop) - self.stop_ids_for_agency = resources.AsyncStopIDsForAgencyResourceWithStreamingResponse( - client.stop_ids_for_agency - ) - self.schedule_for_stop = resources.AsyncScheduleForStopResourceWithStreamingResponse(client.schedule_for_stop) - self.route = resources.AsyncRouteResourceWithStreamingResponse(client.route) - self.arrival_and_departure = resources.AsyncArrivalAndDepartureResourceWithStreamingResponse( - client.arrival_and_departure - ) - self.trip = resources.AsyncTripResourceWithStreamingResponse(client.trip) - self.trips_for_location = resources.AsyncTripsForLocationResourceWithStreamingResponse( - client.trips_for_location - ) - self.trip_details = resources.AsyncTripDetailsResourceWithStreamingResponse(client.trip_details) - self.trip_for_vehicle = resources.AsyncTripForVehicleResourceWithStreamingResponse(client.trip_for_vehicle) + self._client = client + + @cached_property + def agencies_with_coverage(self) -> agencies_with_coverage.AsyncAgenciesWithCoverageResourceWithStreamingResponse: + from .resources.agencies_with_coverage import AsyncAgenciesWithCoverageResourceWithStreamingResponse + + return AsyncAgenciesWithCoverageResourceWithStreamingResponse(self._client.agencies_with_coverage) + + @cached_property + def agency(self) -> agency.AsyncAgencyResourceWithStreamingResponse: + from .resources.agency import AsyncAgencyResourceWithStreamingResponse + + return AsyncAgencyResourceWithStreamingResponse(self._client.agency) + + @cached_property + def vehicles_for_agency(self) -> vehicles_for_agency.AsyncVehiclesForAgencyResourceWithStreamingResponse: + from .resources.vehicles_for_agency import AsyncVehiclesForAgencyResourceWithStreamingResponse + + return AsyncVehiclesForAgencyResourceWithStreamingResponse(self._client.vehicles_for_agency) + + @cached_property + def config(self) -> config.AsyncConfigResourceWithStreamingResponse: + from .resources.config import AsyncConfigResourceWithStreamingResponse + + return AsyncConfigResourceWithStreamingResponse(self._client.config) + + @cached_property + def current_time(self) -> current_time.AsyncCurrentTimeResourceWithStreamingResponse: + from .resources.current_time import AsyncCurrentTimeResourceWithStreamingResponse + + return AsyncCurrentTimeResourceWithStreamingResponse(self._client.current_time) + + @cached_property + def stops_for_location(self) -> stops_for_location.AsyncStopsForLocationResourceWithStreamingResponse: + from .resources.stops_for_location import AsyncStopsForLocationResourceWithStreamingResponse + + return AsyncStopsForLocationResourceWithStreamingResponse(self._client.stops_for_location) + + @cached_property + def stops_for_route(self) -> stops_for_route.AsyncStopsForRouteResourceWithStreamingResponse: + from .resources.stops_for_route import AsyncStopsForRouteResourceWithStreamingResponse + + return AsyncStopsForRouteResourceWithStreamingResponse(self._client.stops_for_route) + + @cached_property + def stops_for_agency(self) -> stops_for_agency.AsyncStopsForAgencyResourceWithStreamingResponse: + from .resources.stops_for_agency import AsyncStopsForAgencyResourceWithStreamingResponse + + return AsyncStopsForAgencyResourceWithStreamingResponse(self._client.stops_for_agency) + + @cached_property + def stop(self) -> stop.AsyncStopResourceWithStreamingResponse: + from .resources.stop import AsyncStopResourceWithStreamingResponse + + return AsyncStopResourceWithStreamingResponse(self._client.stop) + + @cached_property + def stop_ids_for_agency(self) -> stop_ids_for_agency.AsyncStopIDsForAgencyResourceWithStreamingResponse: + from .resources.stop_ids_for_agency import AsyncStopIDsForAgencyResourceWithStreamingResponse + + return AsyncStopIDsForAgencyResourceWithStreamingResponse(self._client.stop_ids_for_agency) + + @cached_property + def schedule_for_stop(self) -> schedule_for_stop.AsyncScheduleForStopResourceWithStreamingResponse: + from .resources.schedule_for_stop import AsyncScheduleForStopResourceWithStreamingResponse + + return AsyncScheduleForStopResourceWithStreamingResponse(self._client.schedule_for_stop) + + @cached_property + def route(self) -> route.AsyncRouteResourceWithStreamingResponse: + from .resources.route import AsyncRouteResourceWithStreamingResponse + + return AsyncRouteResourceWithStreamingResponse(self._client.route) + + @cached_property + def route_ids_for_agency(self) -> route_ids_for_agency.AsyncRouteIDsForAgencyResourceWithStreamingResponse: + from .resources.route_ids_for_agency import AsyncRouteIDsForAgencyResourceWithStreamingResponse + + return AsyncRouteIDsForAgencyResourceWithStreamingResponse(self._client.route_ids_for_agency) + + @cached_property + def routes_for_location(self) -> routes_for_location.AsyncRoutesForLocationResourceWithStreamingResponse: + from .resources.routes_for_location import AsyncRoutesForLocationResourceWithStreamingResponse + + return AsyncRoutesForLocationResourceWithStreamingResponse(self._client.routes_for_location) + + @cached_property + def routes_for_agency(self) -> routes_for_agency.AsyncRoutesForAgencyResourceWithStreamingResponse: + from .resources.routes_for_agency import AsyncRoutesForAgencyResourceWithStreamingResponse + + return AsyncRoutesForAgencyResourceWithStreamingResponse(self._client.routes_for_agency) + + @cached_property + def schedule_for_route(self) -> schedule_for_route.AsyncScheduleForRouteResourceWithStreamingResponse: + from .resources.schedule_for_route import AsyncScheduleForRouteResourceWithStreamingResponse + + return AsyncScheduleForRouteResourceWithStreamingResponse(self._client.schedule_for_route) + + @cached_property + def arrival_and_departure(self) -> arrival_and_departure.AsyncArrivalAndDepartureResourceWithStreamingResponse: + from .resources.arrival_and_departure import AsyncArrivalAndDepartureResourceWithStreamingResponse + + return AsyncArrivalAndDepartureResourceWithStreamingResponse(self._client.arrival_and_departure) + + @cached_property + def trip(self) -> trip.AsyncTripResourceWithStreamingResponse: + from .resources.trip import AsyncTripResourceWithStreamingResponse + + return AsyncTripResourceWithStreamingResponse(self._client.trip) + + @cached_property + def trips_for_location(self) -> trips_for_location.AsyncTripsForLocationResourceWithStreamingResponse: + from .resources.trips_for_location import AsyncTripsForLocationResourceWithStreamingResponse + + return AsyncTripsForLocationResourceWithStreamingResponse(self._client.trips_for_location) + + @cached_property + def trip_details(self) -> trip_details.AsyncTripDetailsResourceWithStreamingResponse: + from .resources.trip_details import AsyncTripDetailsResourceWithStreamingResponse + + return AsyncTripDetailsResourceWithStreamingResponse(self._client.trip_details) + + @cached_property + def trip_for_vehicle(self) -> trip_for_vehicle.AsyncTripForVehicleResourceWithStreamingResponse: + from .resources.trip_for_vehicle import AsyncTripForVehicleResourceWithStreamingResponse + + return AsyncTripForVehicleResourceWithStreamingResponse(self._client.trip_for_vehicle) + + @cached_property + def trips_for_route(self) -> trips_for_route.AsyncTripsForRouteResourceWithStreamingResponse: + from .resources.trips_for_route import AsyncTripsForRouteResourceWithStreamingResponse + + return AsyncTripsForRouteResourceWithStreamingResponse(self._client.trips_for_route) + + @cached_property + def report_problem_with_stop( + self, + ) -> report_problem_with_stop.AsyncReportProblemWithStopResourceWithStreamingResponse: + from .resources.report_problem_with_stop import AsyncReportProblemWithStopResourceWithStreamingResponse + + return AsyncReportProblemWithStopResourceWithStreamingResponse(self._client.report_problem_with_stop) + + @cached_property + def report_problem_with_trip( + self, + ) -> report_problem_with_trip.AsyncReportProblemWithTripResourceWithStreamingResponse: + from .resources.report_problem_with_trip import AsyncReportProblemWithTripResourceWithStreamingResponse + + return AsyncReportProblemWithTripResourceWithStreamingResponse(self._client.report_problem_with_trip) + + @cached_property + def search_for_stop(self) -> search_for_stop.AsyncSearchForStopResourceWithStreamingResponse: + from .resources.search_for_stop import AsyncSearchForStopResourceWithStreamingResponse + + return AsyncSearchForStopResourceWithStreamingResponse(self._client.search_for_stop) + + @cached_property + def search_for_route(self) -> search_for_route.AsyncSearchForRouteResourceWithStreamingResponse: + from .resources.search_for_route import AsyncSearchForRouteResourceWithStreamingResponse + + return AsyncSearchForRouteResourceWithStreamingResponse(self._client.search_for_route) + + @cached_property + def block(self) -> block.AsyncBlockResourceWithStreamingResponse: + from .resources.block import AsyncBlockResourceWithStreamingResponse + + return AsyncBlockResourceWithStreamingResponse(self._client.block) + + @cached_property + def shape(self) -> shape.AsyncShapeResourceWithStreamingResponse: + from .resources.shape import AsyncShapeResourceWithStreamingResponse + + return AsyncShapeResourceWithStreamingResponse(self._client.shape) Client = OnebusawaySDK diff --git a/src/onebusaway/_compat.py b/src/onebusaway/_compat.py index c919b5ad..e6690a4f 100644 --- a/src/onebusaway/_compat.py +++ b/src/onebusaway/_compat.py @@ -2,24 +2,23 @@ from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload from datetime import date, datetime -from typing_extensions import Self +from typing_extensions import Self, Literal, TypedDict import pydantic from pydantic.fields import FieldInfo -from ._types import StrBytesIntFloat +from ._types import IncEx, StrBytesIntFloat _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,137 +43,150 @@ 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) + + +class _ModelDumpKwargs(TypedDict, total=False): + by_alias: bool def model_dump( model: pydantic.BaseModel, *, + exclude: IncEx | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, + warnings: bool = True, + mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: - if PYDANTIC_V2: + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): + kwargs: _ModelDumpKwargs = {} + if by_alias is not None: + kwargs["by_alias"] = by_alias return model.model_dump( + mode=mode, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=True if PYDANTIC_V1 else warnings, + **kwargs, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - 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 if TYPE_CHECKING: - class GenericModel(pydantic.BaseModel): - ... + 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): - ... + class GenericModel(pydantic.BaseModel): ... # cached properties @@ -193,30 +205,22 @@ class typed_cached_property(Generic[_T]): func: Callable[[Any], _T] attrname: str | None - def __init__(self, func: Callable[[Any], _T]) -> None: - ... + def __init__(self, func: Callable[[Any], _T]) -> None: ... @overload - def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: - ... + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... @overload - def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: - ... + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: raise NotImplementedError() - def __set_name__(self, owner: type[Any], name: str) -> None: - ... + def __set_name__(self, owner: type[Any], name: str) -> None: ... # __set__ is not defined at runtime, but @cached_property is designed to be settable - def __set__(self, instance: object, value: _T) -> None: - ... + def __set__(self, instance: object, value: _T) -> None: ... else: - try: - from functools import cached_property as cached_property - except ImportError: - from cached_property import cached_property as cached_property + from functools import cached_property as cached_property typed_cached_property = cached_property diff --git a/src/onebusaway/_constants.py b/src/onebusaway/_constants.py index a2ac3b6f..6ddf2c71 100644 --- a/src/onebusaway/_constants.py +++ b/src/onebusaway/_constants.py @@ -6,7 +6,7 @@ OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 1 minute -DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) DEFAULT_MAX_RETRIES = 2 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) diff --git a/src/onebusaway/_files.py b/src/onebusaway/_files.py index 0d2022ae..cc14c14f 100644 --- a/src/onebusaway/_files.py +++ b/src/onebusaway/_files.py @@ -39,13 +39,11 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: @overload -def to_httpx_files(files: None) -> None: - ... +def to_httpx_files(files: None) -> None: ... @overload -def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: - ... +def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: @@ -71,25 +69,23 @@ 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 @overload -async def async_to_httpx_files(files: None) -> None: - ... +async def async_to_httpx_files(files: None) -> None: ... @overload -async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: - ... +async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: @@ -115,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/onebusaway/_models.py b/src/onebusaway/_models.py index 5148d5a7..29070e05 100644 --- a/src/onebusaway/_models.py +++ b/src/onebusaway/_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, @@ -19,7 +34,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( @@ -37,6 +51,7 @@ PropertyInfo, is_list, is_given, + json_safe, lru_cache, is_mapping, parse_date, @@ -45,10 +60,11 @@ strip_not_given, extract_type_arg, is_annotated_type, + is_type_alias_type, strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -63,7 +79,7 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: - from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema __all__ = ["BaseModel", "GenericModel"] @@ -79,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 @@ -93,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, @@ -170,21 +186,21 @@ def to_json( @override def __str__(self) -> str: # mypy complains about an invalid self arg - return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc] + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] # Override the 'construct' method in a way that supports recursive parsing without validation. # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. @classmethod @override - def construct( - cls: Type[ModelT], + def construct( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], _fields_set: set[str] | None = None, **values: object, ) -> ModelT: - m = cls.__new__(cls) + m = __cls.__new__(__cls) fields_values: dict[str, object] = {} - config = get_model_config(cls) + config = get_model_config(__cls) populate_by_name = ( config.allow_population_by_field_name if isinstance(config, _ConfigProtocol) @@ -194,7 +210,7 @@ def construct( if _fields_set is None: _fields_set = set() - model_fields = get_model_fields(cls) + model_fields = get_model_fields(__cls) for name, field in model_fields.items(): key = field.alias if key is None or (key not in values and populate_by_name): @@ -206,28 +222,32 @@ def construct( 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 @@ -237,7 +257,7 @@ def construct( # 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 @@ -248,15 +268,17 @@ def model_dump( self, *, mode: Literal["json", "python"] | str = "python", - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + 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 @@ -265,22 +287,30 @@ 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. """ - if mode != "python": - raise ValueError("mode is only supported in Pydantic v2") + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") if round_trip != False: raise ValueError("round_trip is only supported in Pydantic v2") if warnings != True: @@ -289,29 +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") - return super().dict( # pyright: ignore[reportDeprecated] + 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 + @override def model_dump_json( self, *, indent: int | None = None, - include: IncEx = None, - exclude: IncEx = None, - by_alias: bool = False, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + 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 @@ -340,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, @@ -355,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: @@ -380,6 +442,8 @@ def is_basemodel(type_: type) -> bool: def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: origin = get_origin(type_) or type_ + if not inspect.isclass(origin): + return False return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) @@ -415,18 +479,28 @@ 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. """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] + 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() @@ -438,7 +512,7 @@ def construct_type(*, value: object, type_: object) -> object: if is_union(origin): try: - return validate_type(type_=cast("type[object]", type_), value=value) + return validate_type(type_=cast("type[object]", original_type or type_), value=value) except Exception: pass @@ -480,7 +554,11 @@ def construct_type(*, value: object, type_: object) -> object: _, items_type = get_args(type_) # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - if not is_literal_type(type_) and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)): + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): if is_list(value): return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] @@ -526,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. @@ -568,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 @@ -587,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") + + field_schema = field["schema"] - if field_info.annotation and is_literal_type(field_info.annotation): - for entry in get_args(field_info.annotation): + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -622,21 +704,24 @@ 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 def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + if schema["type"] != "model": return None + schema = cast("ModelSchema", schema) fields_schema = schema["schema"] if fields_schema["type"] != "model-fields": return None fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) if not field: return None @@ -660,7 +745,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: setattr(typ, "__pydantic_config__", config) # noqa: B010 -# our use of subclasssing here causes weirdness for type checkers, +# our use of subclassing here causes weirdness for type checkers, # so we just pretend that we don't subclass if TYPE_CHECKING: GenericModel = BaseModel @@ -670,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)) @@ -715,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 @@ -730,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): @@ -774,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/onebusaway/_qs.py b/src/onebusaway/_qs.py index 274320ca..de8c99bc 100644 --- a/src/onebusaway/_qs.py +++ b/src/onebusaway/_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, @@ -101,7 +101,10 @@ def _stringify_item( items.extend(self._stringify_item(key, item, opts)) return items elif array_format == "indices": - raise NotImplementedError("The array indices format is not supported yet") + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items elif array_format == "brackets": items = [] key = key + "[]" @@ -143,8 +146,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/onebusaway/_response.py b/src/onebusaway/_response.py index 40d4fb25..5c836034 100644 --- a/src/onebusaway/_response.py +++ b/src/onebusaway/_response.py @@ -25,7 +25,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -55,6 +55,9 @@ class BaseAPIResponse(Generic[R]): http_response: httpx.Response + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + def __init__( self, *, @@ -64,6 +67,7 @@ def __init__( stream: bool, stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, options: FinalRequestOptions, + retries_taken: int = 0, ) -> None: self._cast_to = cast_to self._client = client @@ -72,6 +76,7 @@ def __init__( self._stream_cls = stream_cls self._options = options self.http_response = raw + self.retries_taken = retries_taken @property def headers(self) -> httpx.Headers: @@ -121,9 +126,17 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to if self._is_sse_stream: if to: @@ -139,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, ), ) @@ -149,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, ), ) @@ -159,18 +174,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) @@ -187,7 +197,8 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == float: return cast(R, float(response.text)) - origin = get_origin(cast_to) or cast_to + if cast_to == bool: + return cast(R, response.text.lower() == "true") if origin == APIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") @@ -202,7 +213,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError( "Pydantic models must subclass our base model type, e.g. `from onebusaway import BaseModel`" ) @@ -221,7 +238,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() @@ -257,12 +274,10 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: class APIResponse(BaseAPIResponse[R]): @overload - def parse(self, *, to: type[_T]) -> _T: - ... + def parse(self, *, to: type[_T]) -> _T: ... @overload - def parse(self) -> R: - ... + def parse(self) -> R: ... def parse(self, *, to: type[_T] | None = None) -> R | _T: """Returns the rich python representation of this response's data. @@ -361,12 +376,10 @@ def iter_lines(self) -> Iterator[str]: class AsyncAPIResponse(BaseAPIResponse[R]): @overload - async def parse(self, *, to: type[_T]) -> _T: - ... + async def parse(self, *, to: type[_T]) -> _T: ... @overload - async def parse(self) -> R: - ... + async def parse(self) -> R: ... async def parse(self, *, to: type[_T] | None = None) -> R | _T: """Returns the rich python representation of this response's data. diff --git a/src/onebusaway/_streaming.py b/src/onebusaway/_streaming.py index 957b8da3..6a64d816 100644 --- a/src/onebusaway/_streaming.py +++ b/src/onebusaway/_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 OnebusawaySDK, AsyncOnebusawaySDK + 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: OnebusawaySDK, + 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: AsyncOnebusawaySDK, + 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/onebusaway/_types.py b/src/onebusaway/_types.py index 66ec7fc2..73b0d5e5 100644 --- a/src/onebusaway/_types.py +++ b/src/onebusaway/_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 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,24 +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 ``` """ @@ -129,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 @@ -145,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}) ``` """ @@ -154,6 +178,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod @@ -162,16 +189,14 @@ def build( *, response: Response, data: object, - ) -> _T: - ... + ) -> _T: ... Headers = Mapping[str, Union[str, Omit]] class HeadersLikeProtocol(Protocol): - def get(self, __key: str) -> str | None: - ... + def get(self, __key: str) -> str | None: ... HeadersLike = Union[Headers, HeadersLikeProtocol] @@ -195,8 +220,8 @@ def get(self, __key: str) -> str | None: StrBytesIntFloat = Union[str, bytes, int, float] # Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49 -IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None" +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] PostParser = Callable[[Any], Any] @@ -218,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/onebusaway/_utils/__init__.py b/src/onebusaway/_utils/__init__.py index 3efe66c8..10cb66d2 100644 --- a/src/onebusaway/_utils/__init__.py +++ b/src/onebusaway/_utils/__init__.py @@ -1,3 +1,4 @@ +from ._path import path_template as path_template from ._sync import asyncify as asyncify from ._proxy import LazyProxy as LazyProxy from ._utils import ( @@ -6,10 +7,10 @@ is_list as is_list, is_given as is_given, is_tuple as is_tuple, + json_safe as json_safe, 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, @@ -22,7 +23,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, @@ -31,13 +31,22 @@ 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, extract_type_var_from_base as extract_type_var_from_base, ) @@ -53,3 +62,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/onebusaway/_utils/_compat.py b/src/onebusaway/_utils/_compat.py new file mode 100644 index 00000000..2c70b299 --- /dev/null +++ b/src/onebusaway/_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/onebusaway/_utils/_datetime_parse.py b/src/onebusaway/_utils/_datetime_parse.py new file mode 100644 index 00000000..7cb9d9e6 --- /dev/null +++ b/src/onebusaway/_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/onebusaway/_utils/_json.py b/src/onebusaway/_utils/_json.py new file mode 100644 index 00000000..60584214 --- /dev/null +++ b/src/onebusaway/_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/onebusaway/_utils/_path.py b/src/onebusaway/_utils/_path.py new file mode 100644 index 00000000..4d6e1e4c --- /dev/null +++ b/src/onebusaway/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/onebusaway/_utils/_proxy.py b/src/onebusaway/_utils/_proxy.py index c46a62a6..0f239a33 100644 --- a/src/onebusaway/_utils/_proxy.py +++ b/src/onebusaway/_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__ @@ -59,5 +62,4 @@ def __as_proxied__(self) -> T: return cast(T, self) @abstractmethod - def __load__(self) -> T: - ... + def __load__(self) -> T: ... diff --git a/src/onebusaway/_utils/_reflection.py b/src/onebusaway/_utils/_reflection.py index 9a53c7bd..89aa712a 100644 --- a/src/onebusaway/_utils/_reflection.py +++ b/src/onebusaway/_utils/_reflection.py @@ -34,7 +34,7 @@ def assert_signatures_in_sync( if custom_param.annotation != source_param.annotation: errors.append( - f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(source_param.annotation)}" + f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}" ) continue diff --git a/src/onebusaway/_utils/_resources_proxy.py b/src/onebusaway/_utils/_resources_proxy.py new file mode 100644 index 00000000..c0f77839 --- /dev/null +++ b/src/onebusaway/_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 `onebusaway.resources` module. + + This is used so that we can lazily import `onebusaway.resources` only when + needed *and* so that users can just import `onebusaway` and reference `onebusaway.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("onebusaway.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/onebusaway/_utils/_sync.py b/src/onebusaway/_utils/_sync.py index d0d81033..f6027c18 100644 --- a/src/onebusaway/_utils/_sync.py +++ b/src/onebusaway/_utils/_sync.py @@ -1,56 +1,49 @@ from __future__ import annotations +import asyncio import functools from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio +import sniffio import anyio.to_thread -from ._reflection import function_has_argument - T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") -# copied from `asyncer`, https://github.com/tiangolo/asyncer -def asyncify( - function: Callable[T_ParamSpec, T_Retval], - *, - cancellable: bool = False, - limiter: anyio.CapacityLimiter | None = None, -) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: +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 anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +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, and that when called, calls the original function - in a worker thread using `anyio.to_thread.run_sync()`. Internally, - `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports - keyword arguments additional to positional arguments and it adds better support for - autocompletion and inline errors for the arguments of the function called and the - return value. - - If the `cancellable` option is enabled and the task waiting for its completion is - cancelled, the thread will still run its course but its return value (or any raised - exception) will be ignored. + positional and keyword arguments. - Use it like this: + Usage: - ```Python - def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: - # Do work - return "Some result" + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result - result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") - print(result) + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) ``` ## Arguments `function`: a blocking regular callable (e.g. a function) - `cancellable`: `True` to allow cancellation of the operation - `limiter`: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) ## Return @@ -60,22 +53,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: """ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - partial_f = functools.partial(function, *args, **kwargs) - - # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old - # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid - # surfacing deprecation warnings. - if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"): - return await anyio.to_thread.run_sync( - partial_f, - abandon_on_cancel=cancellable, - limiter=limiter, - ) - - return await anyio.to_thread.run_sync( - partial_f, - cancellable=cancellable, - limiter=limiter, - ) + return await to_thread(function, *args, **kwargs) return wrapper diff --git a/src/onebusaway/_utils/_transform.py b/src/onebusaway/_utils/_transform.py index 47e262a5..52075492 100644 --- a/src/onebusaway/_utils/_transform.py +++ b/src/onebusaway/_utils/_transform.py @@ -5,27 +5,31 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + 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 model_dump, is_typeddict _T = TypeVar("_T") @@ -108,6 +112,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -126,7 +131,7 @@ def _get_annotated_type(type_: type) -> type | None: def _maybe_transform_key(key: str, type_: type) -> str: """Transform the given `data` based on the annotations provided in `type_`. - Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata. + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. """ annotated_type = _get_annotated_type(type_) if annotated_type is None: @@ -142,6 +147,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -160,20 +169,43 @@ 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 stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return _transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (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. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -186,7 +218,7 @@ def _transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json") annotated_type = _get_annotated_type(annotation) if annotated_type is None: @@ -235,6 +267,11 @@ def _transform_typeddict( result: dict[str, object] = {} 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 omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -298,20 +335,43 @@ 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 stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type if is_typeddict(stripped_type) and is_mapping(data): return await _async_transform_typeddict(data, stripped_type) + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + if ( # List[T] (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. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -324,7 +384,7 @@ async def _async_transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json") annotated_type = _get_annotated_type(annotation) if annotated_type is None: @@ -373,6 +433,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} 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 omitted values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -380,3 +445,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/onebusaway/_utils/_typing.py b/src/onebusaway/_utils/_typing.py index c036991f..193109f3 100644 --- a/src/onebusaway/_utils/_typing.py +++ b/src/onebusaway/_utils/_typing.py @@ -1,11 +1,21 @@ from __future__ import annotations +import sys +import typing +import typing_extensions from typing import Any, TypeVar, Iterable, cast from collections import abc as _c_abc -from typing_extensions import Required, Annotated, get_args, get_origin - +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +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: @@ -16,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 @@ -36,7 +51,28 @@ def is_typevar(typ: type) -> bool: return type(typ) == TypeVar # type: ignore +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) @@ -79,7 +115,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/onebusaway/_utils/_utils.py b/src/onebusaway/_utils/_utils.py index 34797c29..eec7f4a1 100644 --- a/src/onebusaway/_utils/_utils.py +++ b/src/onebusaway/_utils/_utils.py @@ -16,12 +16,12 @@ overload, ) from pathlib import Path +from datetime import date, datetime from typing_extensions import TypeGuard 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, ...]) @@ -63,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 [] @@ -71,8 +71,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 @@ -118,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 @@ -211,20 +219,17 @@ def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: Example usage: ```py @overload - def foo(*, a: str) -> str: - ... + def foo(*, a: str) -> str: ... @overload - def foo(*, b: bool) -> str: - ... + def foo(*, b: bool) -> str: ... # This enforces the same constraints that a static type checker would # i.e. that either a or b must be passed to the function @required_args(["a"], ["b"]) - def foo(*, a: str | None = None, b: bool | None = None) -> str: - ... + def foo(*, a: str | None = None, b: bool | None = None) -> str: ... ``` """ @@ -286,18 +291,15 @@ def wrapper(*args: object, **kwargs: object) -> object: @overload -def strip_not_given(obj: None) -> None: - ... +def strip_not_given(obj: None) -> None: ... @overload -def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: - ... +def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ... @overload -def strip_not_given(obj: object) -> object: - ... +def strip_not_given(obj: object) -> object: ... def strip_not_given(obj: object | None) -> object: @@ -369,12 +371,13 @@ def file_from_path(path: str) -> FileTypes: def get_required_header(headers: HeadersLike, header: str) -> str: lower_header = header.lower() - if isinstance(headers, Mapping): - for k, v in headers.items(): + if is_mapping_t(headers): + # mypy doesn't understand the type narrowing here + for k, v in headers.items(): # type: ignore if k.lower() == lower_header and isinstance(v, str): return v - """ to deal with the case where the header looks like Stainless-Event-Id """ + # to deal with the case where the header looks like Stainless-Event-Id intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize()) for normalized_header in [header, lower_header, header.upper(), intercaps_header]: @@ -400,3 +403,19 @@ def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: maxsize=maxsize, ) return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data diff --git a/src/onebusaway/_version.py b/src/onebusaway/_version.py index ffc4f0c7..fb204a99 100644 --- a/src/onebusaway/_version.py +++ b/src/onebusaway/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "onebusaway" -__version__ = "0.1.0-alpha.10" # x-release-please-version +__version__ = "1.23.0" # x-release-please-version diff --git a/src/onebusaway/resources/__init__.py b/src/onebusaway/resources/__init__.py index 48fb9f8b..0a958d2a 100644 --- a/src/onebusaway/resources/__init__.py +++ b/src/onebusaway/resources/__init__.py @@ -16,6 +16,14 @@ TripResourceWithStreamingResponse, AsyncTripResourceWithStreamingResponse, ) +from .block import ( + BlockResource, + AsyncBlockResource, + BlockResourceWithRawResponse, + AsyncBlockResourceWithRawResponse, + BlockResourceWithStreamingResponse, + AsyncBlockResourceWithStreamingResponse, +) from .route import ( RouteResource, AsyncRouteResource, @@ -24,6 +32,14 @@ RouteResourceWithStreamingResponse, AsyncRouteResourceWithStreamingResponse, ) +from .shape import ( + ShapeResource, + AsyncShapeResource, + ShapeResourceWithRawResponse, + AsyncShapeResourceWithRawResponse, + ShapeResourceWithStreamingResponse, + AsyncShapeResourceWithStreamingResponse, +) from .agency import ( AgencyResource, AsyncAgencyResource, @@ -56,6 +72,14 @@ TripDetailsResourceWithStreamingResponse, AsyncTripDetailsResourceWithStreamingResponse, ) +from .search_for_stop import ( + SearchForStopResource, + AsyncSearchForStopResource, + SearchForStopResourceWithRawResponse, + AsyncSearchForStopResourceWithRawResponse, + SearchForStopResourceWithStreamingResponse, + AsyncSearchForStopResourceWithStreamingResponse, +) from .stops_for_route import ( StopsForRouteResource, AsyncStopsForRouteResource, @@ -64,6 +88,30 @@ StopsForRouteResourceWithStreamingResponse, AsyncStopsForRouteResourceWithStreamingResponse, ) +from .trips_for_route import ( + TripsForRouteResource, + AsyncTripsForRouteResource, + TripsForRouteResourceWithRawResponse, + AsyncTripsForRouteResourceWithRawResponse, + TripsForRouteResourceWithStreamingResponse, + AsyncTripsForRouteResourceWithStreamingResponse, +) +from .search_for_route import ( + SearchForRouteResource, + AsyncSearchForRouteResource, + SearchForRouteResourceWithRawResponse, + AsyncSearchForRouteResourceWithRawResponse, + SearchForRouteResourceWithStreamingResponse, + AsyncSearchForRouteResourceWithStreamingResponse, +) +from .stops_for_agency import ( + StopsForAgencyResource, + AsyncStopsForAgencyResource, + StopsForAgencyResourceWithRawResponse, + AsyncStopsForAgencyResourceWithRawResponse, + StopsForAgencyResourceWithStreamingResponse, + AsyncStopsForAgencyResourceWithStreamingResponse, +) from .trip_for_vehicle import ( TripForVehicleResource, AsyncTripForVehicleResource, @@ -72,6 +120,14 @@ TripForVehicleResourceWithStreamingResponse, AsyncTripForVehicleResourceWithStreamingResponse, ) +from .routes_for_agency import ( + RoutesForAgencyResource, + AsyncRoutesForAgencyResource, + RoutesForAgencyResourceWithRawResponse, + AsyncRoutesForAgencyResourceWithRawResponse, + RoutesForAgencyResourceWithStreamingResponse, + AsyncRoutesForAgencyResourceWithStreamingResponse, +) from .schedule_for_stop import ( ScheduleForStopResource, AsyncScheduleForStopResource, @@ -80,6 +136,14 @@ ScheduleForStopResourceWithStreamingResponse, AsyncScheduleForStopResourceWithStreamingResponse, ) +from .schedule_for_route import ( + ScheduleForRouteResource, + AsyncScheduleForRouteResource, + ScheduleForRouteResourceWithRawResponse, + AsyncScheduleForRouteResourceWithRawResponse, + ScheduleForRouteResourceWithStreamingResponse, + AsyncScheduleForRouteResourceWithStreamingResponse, +) from .stops_for_location import ( StopsForLocationResource, AsyncStopsForLocationResource, @@ -96,6 +160,14 @@ TripsForLocationResourceWithStreamingResponse, AsyncTripsForLocationResourceWithStreamingResponse, ) +from .routes_for_location import ( + RoutesForLocationResource, + AsyncRoutesForLocationResource, + RoutesForLocationResourceWithRawResponse, + AsyncRoutesForLocationResourceWithRawResponse, + RoutesForLocationResourceWithStreamingResponse, + AsyncRoutesForLocationResourceWithStreamingResponse, +) from .stop_ids_for_agency import ( StopIDsForAgencyResource, AsyncStopIDsForAgencyResource, @@ -112,6 +184,14 @@ VehiclesForAgencyResourceWithStreamingResponse, AsyncVehiclesForAgencyResourceWithStreamingResponse, ) +from .route_ids_for_agency import ( + RouteIDsForAgencyResource, + AsyncRouteIDsForAgencyResource, + RouteIDsForAgencyResourceWithRawResponse, + AsyncRouteIDsForAgencyResourceWithRawResponse, + RouteIDsForAgencyResourceWithStreamingResponse, + AsyncRouteIDsForAgencyResourceWithStreamingResponse, +) from .arrival_and_departure import ( ArrivalAndDepartureResource, AsyncArrivalAndDepartureResource, @@ -128,6 +208,22 @@ AgenciesWithCoverageResourceWithStreamingResponse, AsyncAgenciesWithCoverageResourceWithStreamingResponse, ) +from .report_problem_with_stop import ( + ReportProblemWithStopResource, + AsyncReportProblemWithStopResource, + ReportProblemWithStopResourceWithRawResponse, + AsyncReportProblemWithStopResourceWithRawResponse, + ReportProblemWithStopResourceWithStreamingResponse, + AsyncReportProblemWithStopResourceWithStreamingResponse, +) +from .report_problem_with_trip import ( + ReportProblemWithTripResource, + AsyncReportProblemWithTripResource, + ReportProblemWithTripResourceWithRawResponse, + AsyncReportProblemWithTripResourceWithRawResponse, + ReportProblemWithTripResourceWithStreamingResponse, + AsyncReportProblemWithTripResourceWithStreamingResponse, +) __all__ = [ "AgenciesWithCoverageResource", @@ -172,6 +268,12 @@ "AsyncStopsForRouteResourceWithRawResponse", "StopsForRouteResourceWithStreamingResponse", "AsyncStopsForRouteResourceWithStreamingResponse", + "StopsForAgencyResource", + "AsyncStopsForAgencyResource", + "StopsForAgencyResourceWithRawResponse", + "AsyncStopsForAgencyResourceWithRawResponse", + "StopsForAgencyResourceWithStreamingResponse", + "AsyncStopsForAgencyResourceWithStreamingResponse", "StopResource", "AsyncStopResource", "StopResourceWithRawResponse", @@ -196,6 +298,30 @@ "AsyncRouteResourceWithRawResponse", "RouteResourceWithStreamingResponse", "AsyncRouteResourceWithStreamingResponse", + "RouteIDsForAgencyResource", + "AsyncRouteIDsForAgencyResource", + "RouteIDsForAgencyResourceWithRawResponse", + "AsyncRouteIDsForAgencyResourceWithRawResponse", + "RouteIDsForAgencyResourceWithStreamingResponse", + "AsyncRouteIDsForAgencyResourceWithStreamingResponse", + "RoutesForLocationResource", + "AsyncRoutesForLocationResource", + "RoutesForLocationResourceWithRawResponse", + "AsyncRoutesForLocationResourceWithRawResponse", + "RoutesForLocationResourceWithStreamingResponse", + "AsyncRoutesForLocationResourceWithStreamingResponse", + "RoutesForAgencyResource", + "AsyncRoutesForAgencyResource", + "RoutesForAgencyResourceWithRawResponse", + "AsyncRoutesForAgencyResourceWithRawResponse", + "RoutesForAgencyResourceWithStreamingResponse", + "AsyncRoutesForAgencyResourceWithStreamingResponse", + "ScheduleForRouteResource", + "AsyncScheduleForRouteResource", + "ScheduleForRouteResourceWithRawResponse", + "AsyncScheduleForRouteResourceWithRawResponse", + "ScheduleForRouteResourceWithStreamingResponse", + "AsyncScheduleForRouteResourceWithStreamingResponse", "ArrivalAndDepartureResource", "AsyncArrivalAndDepartureResource", "ArrivalAndDepartureResourceWithRawResponse", @@ -226,4 +352,46 @@ "AsyncTripForVehicleResourceWithRawResponse", "TripForVehicleResourceWithStreamingResponse", "AsyncTripForVehicleResourceWithStreamingResponse", + "TripsForRouteResource", + "AsyncTripsForRouteResource", + "TripsForRouteResourceWithRawResponse", + "AsyncTripsForRouteResourceWithRawResponse", + "TripsForRouteResourceWithStreamingResponse", + "AsyncTripsForRouteResourceWithStreamingResponse", + "ReportProblemWithStopResource", + "AsyncReportProblemWithStopResource", + "ReportProblemWithStopResourceWithRawResponse", + "AsyncReportProblemWithStopResourceWithRawResponse", + "ReportProblemWithStopResourceWithStreamingResponse", + "AsyncReportProblemWithStopResourceWithStreamingResponse", + "ReportProblemWithTripResource", + "AsyncReportProblemWithTripResource", + "ReportProblemWithTripResourceWithRawResponse", + "AsyncReportProblemWithTripResourceWithRawResponse", + "ReportProblemWithTripResourceWithStreamingResponse", + "AsyncReportProblemWithTripResourceWithStreamingResponse", + "SearchForStopResource", + "AsyncSearchForStopResource", + "SearchForStopResourceWithRawResponse", + "AsyncSearchForStopResourceWithRawResponse", + "SearchForStopResourceWithStreamingResponse", + "AsyncSearchForStopResourceWithStreamingResponse", + "SearchForRouteResource", + "AsyncSearchForRouteResource", + "SearchForRouteResourceWithRawResponse", + "AsyncSearchForRouteResourceWithRawResponse", + "SearchForRouteResourceWithStreamingResponse", + "AsyncSearchForRouteResourceWithStreamingResponse", + "BlockResource", + "AsyncBlockResource", + "BlockResourceWithRawResponse", + "AsyncBlockResourceWithRawResponse", + "BlockResourceWithStreamingResponse", + "AsyncBlockResourceWithStreamingResponse", + "ShapeResource", + "AsyncShapeResource", + "ShapeResourceWithRawResponse", + "AsyncShapeResourceWithRawResponse", + "ShapeResourceWithStreamingResponse", + "AsyncShapeResourceWithStreamingResponse", ] diff --git a/src/onebusaway/resources/agencies_with_coverage.py b/src/onebusaway/resources/agencies_with_coverage.py index 60fa85ef..08ae516a 100644 --- a/src/onebusaway/resources/agencies_with_coverage.py +++ b/src/onebusaway/resources/agencies_with_coverage.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 ( @@ -14,7 +14,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.agencies_with_coverage_retrieve_response import AgenciesWithCoverageRetrieveResponse +from ..types.agencies_with_coverage_list_response import AgenciesWithCoverageListResponse __all__ = ["AgenciesWithCoverageResource", "AsyncAgenciesWithCoverageResource"] @@ -22,13 +22,24 @@ class AgenciesWithCoverageResource(SyncAPIResource): @cached_property def with_raw_response(self) -> AgenciesWithCoverageResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AgenciesWithCoverageResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AgenciesWithCoverageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AgenciesWithCoverageResourceWithStreamingResponse(self) - def retrieve( + def list( self, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -36,28 +47,42 @@ 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, - ) -> AgenciesWithCoverageRetrieveResponse: - """Retrieve Agencies with Coverage""" + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AgenciesWithCoverageListResponse: + """ + Returns a list of all transit agencies currently supported by OneBusAway along + with the center of their coverage area. + """ return self._get( "/api/where/agencies-with-coverage.json", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AgenciesWithCoverageRetrieveResponse, + cast_to=AgenciesWithCoverageListResponse, ) class AsyncAgenciesWithCoverageResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAgenciesWithCoverageResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncAgenciesWithCoverageResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAgenciesWithCoverageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncAgenciesWithCoverageResourceWithStreamingResponse(self) - async def retrieve( + async def list( self, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -65,15 +90,18 @@ 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, - ) -> AgenciesWithCoverageRetrieveResponse: - """Retrieve Agencies with Coverage""" + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AgenciesWithCoverageListResponse: + """ + Returns a list of all transit agencies currently supported by OneBusAway along + with the center of their coverage area. + """ return await self._get( "/api/where/agencies-with-coverage.json", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AgenciesWithCoverageRetrieveResponse, + cast_to=AgenciesWithCoverageListResponse, ) @@ -81,8 +109,8 @@ class AgenciesWithCoverageResourceWithRawResponse: def __init__(self, agencies_with_coverage: AgenciesWithCoverageResource) -> None: self._agencies_with_coverage = agencies_with_coverage - self.retrieve = to_raw_response_wrapper( - agencies_with_coverage.retrieve, + self.list = to_raw_response_wrapper( + agencies_with_coverage.list, ) @@ -90,8 +118,8 @@ class AsyncAgenciesWithCoverageResourceWithRawResponse: def __init__(self, agencies_with_coverage: AsyncAgenciesWithCoverageResource) -> None: self._agencies_with_coverage = agencies_with_coverage - self.retrieve = async_to_raw_response_wrapper( - agencies_with_coverage.retrieve, + self.list = async_to_raw_response_wrapper( + agencies_with_coverage.list, ) @@ -99,8 +127,8 @@ class AgenciesWithCoverageResourceWithStreamingResponse: def __init__(self, agencies_with_coverage: AgenciesWithCoverageResource) -> None: self._agencies_with_coverage = agencies_with_coverage - self.retrieve = to_streamed_response_wrapper( - agencies_with_coverage.retrieve, + self.list = to_streamed_response_wrapper( + agencies_with_coverage.list, ) @@ -108,6 +136,6 @@ class AsyncAgenciesWithCoverageResourceWithStreamingResponse: def __init__(self, agencies_with_coverage: AsyncAgenciesWithCoverageResource) -> None: self._agencies_with_coverage = agencies_with_coverage - self.retrieve = async_to_streamed_response_wrapper( - agencies_with_coverage.retrieve, + self.list = async_to_streamed_response_wrapper( + agencies_with_coverage.list, ) diff --git a/src/onebusaway/resources/agency.py b/src/onebusaway/resources/agency.py index 0a9c46b0..f877a6f0 100644 --- a/src/onebusaway/resources/agency.py +++ b/src/onebusaway/resources/agency.py @@ -4,7 +4,8 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,10 +23,21 @@ class AgencyResource(SyncAPIResource): @cached_property def with_raw_response(self) -> AgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AgencyResourceWithStreamingResponse(self) def retrieve( @@ -37,7 +49,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, ) -> AgencyRetrieveResponse: """ Retrieve information for a specific transit agency identified by its unique ID. @@ -54,7 +66,7 @@ def retrieve( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return self._get( - f"/api/where/agency/{agency_id}.json", + path_template("/api/where/agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,10 +77,21 @@ def retrieve( class AsyncAgencyResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncAgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncAgencyResourceWithStreamingResponse(self) async def retrieve( @@ -80,7 +103,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, ) -> AgencyRetrieveResponse: """ Retrieve information for a specific transit agency identified by its unique ID. @@ -97,7 +120,7 @@ async def retrieve( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return await self._get( - f"/api/where/agency/{agency_id}.json", + path_template("/api/where/agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/onebusaway/resources/arrival_and_departure.py b/src/onebusaway/resources/arrival_and_departure.py index 349e9af9..b2dce821 100644 --- a/src/onebusaway/resources/arrival_and_departure.py +++ b/src/onebusaway/resources/arrival_and_departure.py @@ -8,11 +8,8 @@ import httpx from ..types import arrival_and_departure_list_params, arrival_and_departure_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -31,10 +28,21 @@ class ArrivalAndDepartureResource(SyncAPIResource): @cached_property def with_raw_response(self) -> ArrivalAndDepartureResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return ArrivalAndDepartureResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> ArrivalAndDepartureResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return ArrivalAndDepartureResourceWithStreamingResponse(self) def retrieve( @@ -43,15 +51,15 @@ def retrieve( *, service_date: int, trip_id: str, - stop_sequence: int | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, - vehicle_id: str | NotGiven = NOT_GIVEN, + stop_sequence: int | Omit = omit, + time: int | Omit = omit, + vehicle_id: 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, ) -> ArrivalAndDepartureRetrieveResponse: """ arrival-and-departure-for-stop @@ -68,7 +76,7 @@ def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return self._get( - f"/api/where/arrival-and-departure-for-stop/{stop_id}.json", + path_template("/api/where/arrival-and-departure-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -92,15 +100,15 @@ def list( self, stop_id: str, *, - minutes_after: int | NotGiven = NOT_GIVEN, - minutes_before: int | NotGiven = NOT_GIVEN, - time: Union[str, datetime] | NotGiven = NOT_GIVEN, + minutes_after: int | Omit = omit, + minutes_before: int | Omit = omit, + time: 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ArrivalAndDepartureListResponse: """ arrivals-and-departures-for-stop @@ -123,7 +131,7 @@ def list( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return self._get( - f"/api/where/arrivals-and-departures-for-stop/{stop_id}.json", + path_template("/api/where/arrivals-and-departures-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -145,10 +153,21 @@ def list( class AsyncArrivalAndDepartureResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncArrivalAndDepartureResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncArrivalAndDepartureResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncArrivalAndDepartureResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncArrivalAndDepartureResourceWithStreamingResponse(self) async def retrieve( @@ -157,15 +176,15 @@ async def retrieve( *, service_date: int, trip_id: str, - stop_sequence: int | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, - vehicle_id: str | NotGiven = NOT_GIVEN, + stop_sequence: int | Omit = omit, + time: int | Omit = omit, + vehicle_id: 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, ) -> ArrivalAndDepartureRetrieveResponse: """ arrival-and-departure-for-stop @@ -182,7 +201,7 @@ async def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return await self._get( - f"/api/where/arrival-and-departure-for-stop/{stop_id}.json", + path_template("/api/where/arrival-and-departure-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -206,15 +225,15 @@ async def list( self, stop_id: str, *, - minutes_after: int | NotGiven = NOT_GIVEN, - minutes_before: int | NotGiven = NOT_GIVEN, - time: Union[str, datetime] | NotGiven = NOT_GIVEN, + minutes_after: int | Omit = omit, + minutes_before: int | Omit = omit, + time: 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, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ArrivalAndDepartureListResponse: """ arrivals-and-departures-for-stop @@ -237,7 +256,7 @@ async def list( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return await self._get( - f"/api/where/arrivals-and-departures-for-stop/{stop_id}.json", + path_template("/api/where/arrivals-and-departures-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/resources/block.py b/src/onebusaway/resources/block.py new file mode 100644 index 00000000..93a2d31c --- /dev/null +++ b/src/onebusaway/resources/block.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +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 .._base_client import make_request_options +from ..types.block_retrieve_response import BlockRetrieveResponse + +__all__ = ["BlockResource", "AsyncBlockResource"] + + +class BlockResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BlockResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return BlockResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BlockResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return BlockResourceWithStreamingResponse(self) + + def retrieve( + self, + block_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, + ) -> BlockRetrieveResponse: + """ + Get details of a specific block by 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 block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + return self._get( + path_template("/api/where/block/{block_id}.json", block_id=block_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BlockRetrieveResponse, + ) + + +class AsyncBlockResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBlockResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncBlockResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBlockResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncBlockResourceWithStreamingResponse(self) + + async def retrieve( + self, + block_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, + ) -> BlockRetrieveResponse: + """ + Get details of a specific block by 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 block_id: + raise ValueError(f"Expected a non-empty value for `block_id` but received {block_id!r}") + return await self._get( + path_template("/api/where/block/{block_id}.json", block_id=block_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BlockRetrieveResponse, + ) + + +class BlockResourceWithRawResponse: + def __init__(self, block: BlockResource) -> None: + self._block = block + + self.retrieve = to_raw_response_wrapper( + block.retrieve, + ) + + +class AsyncBlockResourceWithRawResponse: + def __init__(self, block: AsyncBlockResource) -> None: + self._block = block + + self.retrieve = async_to_raw_response_wrapper( + block.retrieve, + ) + + +class BlockResourceWithStreamingResponse: + def __init__(self, block: BlockResource) -> None: + self._block = block + + self.retrieve = to_streamed_response_wrapper( + block.retrieve, + ) + + +class AsyncBlockResourceWithStreamingResponse: + def __init__(self, block: AsyncBlockResource) -> None: + self._block = block + + self.retrieve = async_to_streamed_response_wrapper( + block.retrieve, + ) diff --git a/src/onebusaway/resources/config.py b/src/onebusaway/resources/config.py index 983c10eb..20193cc5 100644 --- a/src/onebusaway/resources/config.py +++ b/src/onebusaway/resources/config.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 ( @@ -22,10 +22,21 @@ class ConfigResource(SyncAPIResource): @cached_property def with_raw_response(self) -> ConfigResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return ConfigResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> ConfigResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return ConfigResourceWithStreamingResponse(self) def retrieve( @@ -36,7 +47,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, ) -> ConfigRetrieveResponse: """config""" return self._get( @@ -51,10 +62,21 @@ def retrieve( class AsyncConfigResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncConfigResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncConfigResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncConfigResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncConfigResourceWithStreamingResponse(self) async def retrieve( @@ -65,7 +87,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, ) -> ConfigRetrieveResponse: """config""" return await self._get( diff --git a/src/onebusaway/resources/current_time.py b/src/onebusaway/resources/current_time.py index 70f504e1..8760365e 100644 --- a/src/onebusaway/resources/current_time.py +++ b/src/onebusaway/resources/current_time.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 ( @@ -22,10 +22,21 @@ class CurrentTimeResource(SyncAPIResource): @cached_property def with_raw_response(self) -> CurrentTimeResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return CurrentTimeResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> CurrentTimeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return CurrentTimeResourceWithStreamingResponse(self) def retrieve( @@ -36,7 +47,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, ) -> CurrentTimeRetrieveResponse: """current-time""" return self._get( @@ -51,10 +62,21 @@ def retrieve( class AsyncCurrentTimeResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncCurrentTimeResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncCurrentTimeResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncCurrentTimeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncCurrentTimeResourceWithStreamingResponse(self) async def retrieve( @@ -65,7 +87,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, ) -> CurrentTimeRetrieveResponse: """current-time""" return await self._get( diff --git a/src/onebusaway/resources/report_problem_with_stop.py b/src/onebusaway/resources/report_problem_with_stop.py new file mode 100644 index 00000000..071dc6fc --- /dev/null +++ b/src/onebusaway/resources/report_problem_with_stop.py @@ -0,0 +1,225 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..types import report_problem_with_stop_retrieve_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, 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 .._base_client import make_request_options +from ..types.shared.response_wrapper import ResponseWrapper + +__all__ = ["ReportProblemWithStopResource", "AsyncReportProblemWithStopResource"] + + +class ReportProblemWithStopResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ReportProblemWithStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return ReportProblemWithStopResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ReportProblemWithStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return ReportProblemWithStopResourceWithStreamingResponse(self) + + def retrieve( + self, + stop_id: str, + *, + code: Literal["stop_name_wrong", "stop_number_wrong", "stop_location_wrong", "route_or_trip_missing", "other"] + | Omit = omit, + user_comment: str | Omit = omit, + user_lat: float | Omit = omit, + user_location_accuracy: float | Omit = omit, + user_lon: float | 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, + ) -> ResponseWrapper: + """ + Submit a user-generated problem report for a stop + + Args: + code: A string code identifying the nature of the problem + + user_comment: Additional comment text supplied by the user describing the problem + + user_lat: The reporting user’s current latitude + + user_location_accuracy: The reporting user’s location accuracy, in meters + + user_lon: The reporting user’s current longitude + + 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 stop_id: + raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") + return self._get( + path_template("/api/where/report-problem-with-stop/{stop_id}.json", stop_id=stop_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "code": code, + "user_comment": user_comment, + "user_lat": user_lat, + "user_location_accuracy": user_location_accuracy, + "user_lon": user_lon, + }, + report_problem_with_stop_retrieve_params.ReportProblemWithStopRetrieveParams, + ), + ), + cast_to=ResponseWrapper, + ) + + +class AsyncReportProblemWithStopResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncReportProblemWithStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncReportProblemWithStopResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncReportProblemWithStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncReportProblemWithStopResourceWithStreamingResponse(self) + + async def retrieve( + self, + stop_id: str, + *, + code: Literal["stop_name_wrong", "stop_number_wrong", "stop_location_wrong", "route_or_trip_missing", "other"] + | Omit = omit, + user_comment: str | Omit = omit, + user_lat: float | Omit = omit, + user_location_accuracy: float | Omit = omit, + user_lon: float | 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, + ) -> ResponseWrapper: + """ + Submit a user-generated problem report for a stop + + Args: + code: A string code identifying the nature of the problem + + user_comment: Additional comment text supplied by the user describing the problem + + user_lat: The reporting user’s current latitude + + user_location_accuracy: The reporting user’s location accuracy, in meters + + user_lon: The reporting user’s current longitude + + 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 stop_id: + raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") + return await self._get( + path_template("/api/where/report-problem-with-stop/{stop_id}.json", stop_id=stop_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "code": code, + "user_comment": user_comment, + "user_lat": user_lat, + "user_location_accuracy": user_location_accuracy, + "user_lon": user_lon, + }, + report_problem_with_stop_retrieve_params.ReportProblemWithStopRetrieveParams, + ), + ), + cast_to=ResponseWrapper, + ) + + +class ReportProblemWithStopResourceWithRawResponse: + def __init__(self, report_problem_with_stop: ReportProblemWithStopResource) -> None: + self._report_problem_with_stop = report_problem_with_stop + + self.retrieve = to_raw_response_wrapper( + report_problem_with_stop.retrieve, + ) + + +class AsyncReportProblemWithStopResourceWithRawResponse: + def __init__(self, report_problem_with_stop: AsyncReportProblemWithStopResource) -> None: + self._report_problem_with_stop = report_problem_with_stop + + self.retrieve = async_to_raw_response_wrapper( + report_problem_with_stop.retrieve, + ) + + +class ReportProblemWithStopResourceWithStreamingResponse: + def __init__(self, report_problem_with_stop: ReportProblemWithStopResource) -> None: + self._report_problem_with_stop = report_problem_with_stop + + self.retrieve = to_streamed_response_wrapper( + report_problem_with_stop.retrieve, + ) + + +class AsyncReportProblemWithStopResourceWithStreamingResponse: + def __init__(self, report_problem_with_stop: AsyncReportProblemWithStopResource) -> None: + self._report_problem_with_stop = report_problem_with_stop + + self.retrieve = async_to_streamed_response_wrapper( + report_problem_with_stop.retrieve, + ) diff --git a/src/onebusaway/resources/report_problem_with_trip.py b/src/onebusaway/resources/report_problem_with_trip.py new file mode 100644 index 00000000..4c4e4608 --- /dev/null +++ b/src/onebusaway/resources/report_problem_with_trip.py @@ -0,0 +1,279 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..types import report_problem_with_trip_retrieve_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, 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 .._base_client import make_request_options +from ..types.shared.response_wrapper import ResponseWrapper + +__all__ = ["ReportProblemWithTripResource", "AsyncReportProblemWithTripResource"] + + +class ReportProblemWithTripResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ReportProblemWithTripResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return ReportProblemWithTripResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ReportProblemWithTripResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return ReportProblemWithTripResourceWithStreamingResponse(self) + + def retrieve( + self, + trip_id: str, + *, + code: Literal[ + "vehicle_never_came", + "vehicle_came_early", + "vehicle_came_late", + "wrong_headsign", + "vehicle_does_not_stop_here", + "other", + ] + | Omit = omit, + service_date: int | Omit = omit, + stop_id: str | Omit = omit, + user_comment: str | Omit = omit, + user_lat: float | Omit = omit, + user_location_accuracy: float | Omit = omit, + user_lon: float | Omit = omit, + user_on_vehicle: bool | Omit = omit, + user_vehicle_number: str | Omit = omit, + vehicle_id: 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, + ) -> ResponseWrapper: + """ + Submit a user-generated problem report for a particular trip. + + Args: + code: A string code identifying the nature of the problem + + service_date: The service date of the trip + + stop_id: A stop ID indicating where the user is experiencing the problem + + user_comment: Additional comment text supplied by the user describing the problem + + user_lat: The reporting user’s current latitude + + user_location_accuracy: The reporting user’s location accuracy, in meters + + user_lon: The reporting user’s current longitude + + user_on_vehicle: Indicator if the user is on the transit vehicle experiencing the problem + + user_vehicle_number: The vehicle number, as reported by the user + + vehicle_id: The vehicle actively serving the trip + + 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 trip_id: + raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") + return self._get( + path_template("/api/where/report-problem-with-trip/{trip_id}.json", trip_id=trip_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "code": code, + "service_date": service_date, + "stop_id": stop_id, + "user_comment": user_comment, + "user_lat": user_lat, + "user_location_accuracy": user_location_accuracy, + "user_lon": user_lon, + "user_on_vehicle": user_on_vehicle, + "user_vehicle_number": user_vehicle_number, + "vehicle_id": vehicle_id, + }, + report_problem_with_trip_retrieve_params.ReportProblemWithTripRetrieveParams, + ), + ), + cast_to=ResponseWrapper, + ) + + +class AsyncReportProblemWithTripResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncReportProblemWithTripResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncReportProblemWithTripResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncReportProblemWithTripResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncReportProblemWithTripResourceWithStreamingResponse(self) + + async def retrieve( + self, + trip_id: str, + *, + code: Literal[ + "vehicle_never_came", + "vehicle_came_early", + "vehicle_came_late", + "wrong_headsign", + "vehicle_does_not_stop_here", + "other", + ] + | Omit = omit, + service_date: int | Omit = omit, + stop_id: str | Omit = omit, + user_comment: str | Omit = omit, + user_lat: float | Omit = omit, + user_location_accuracy: float | Omit = omit, + user_lon: float | Omit = omit, + user_on_vehicle: bool | Omit = omit, + user_vehicle_number: str | Omit = omit, + vehicle_id: 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, + ) -> ResponseWrapper: + """ + Submit a user-generated problem report for a particular trip. + + Args: + code: A string code identifying the nature of the problem + + service_date: The service date of the trip + + stop_id: A stop ID indicating where the user is experiencing the problem + + user_comment: Additional comment text supplied by the user describing the problem + + user_lat: The reporting user’s current latitude + + user_location_accuracy: The reporting user’s location accuracy, in meters + + user_lon: The reporting user’s current longitude + + user_on_vehicle: Indicator if the user is on the transit vehicle experiencing the problem + + user_vehicle_number: The vehicle number, as reported by the user + + vehicle_id: The vehicle actively serving the trip + + 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 trip_id: + raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") + return await self._get( + path_template("/api/where/report-problem-with-trip/{trip_id}.json", trip_id=trip_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "code": code, + "service_date": service_date, + "stop_id": stop_id, + "user_comment": user_comment, + "user_lat": user_lat, + "user_location_accuracy": user_location_accuracy, + "user_lon": user_lon, + "user_on_vehicle": user_on_vehicle, + "user_vehicle_number": user_vehicle_number, + "vehicle_id": vehicle_id, + }, + report_problem_with_trip_retrieve_params.ReportProblemWithTripRetrieveParams, + ), + ), + cast_to=ResponseWrapper, + ) + + +class ReportProblemWithTripResourceWithRawResponse: + def __init__(self, report_problem_with_trip: ReportProblemWithTripResource) -> None: + self._report_problem_with_trip = report_problem_with_trip + + self.retrieve = to_raw_response_wrapper( + report_problem_with_trip.retrieve, + ) + + +class AsyncReportProblemWithTripResourceWithRawResponse: + def __init__(self, report_problem_with_trip: AsyncReportProblemWithTripResource) -> None: + self._report_problem_with_trip = report_problem_with_trip + + self.retrieve = async_to_raw_response_wrapper( + report_problem_with_trip.retrieve, + ) + + +class ReportProblemWithTripResourceWithStreamingResponse: + def __init__(self, report_problem_with_trip: ReportProblemWithTripResource) -> None: + self._report_problem_with_trip = report_problem_with_trip + + self.retrieve = to_streamed_response_wrapper( + report_problem_with_trip.retrieve, + ) + + +class AsyncReportProblemWithTripResourceWithStreamingResponse: + def __init__(self, report_problem_with_trip: AsyncReportProblemWithTripResource) -> None: + self._report_problem_with_trip = report_problem_with_trip + + self.retrieve = async_to_streamed_response_wrapper( + report_problem_with_trip.retrieve, + ) diff --git a/src/onebusaway/resources/route.py b/src/onebusaway/resources/route.py index 7cf9b32a..4b40004f 100644 --- a/src/onebusaway/resources/route.py +++ b/src/onebusaway/resources/route.py @@ -4,7 +4,8 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,10 +23,21 @@ class RouteResource(SyncAPIResource): @cached_property def with_raw_response(self) -> RouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return RouteResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> RouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return RouteResourceWithStreamingResponse(self) def retrieve( @@ -37,7 +49,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, ) -> RouteRetrieveResponse: """ Retrieve information for a specific route identified by its unique ID. @@ -54,7 +66,7 @@ def retrieve( if not route_id: raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") return self._get( - f"/api/where/route/{route_id}.json", + path_template("/api/where/route/{route_id}.json", route_id=route_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,10 +77,21 @@ def retrieve( class AsyncRouteResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncRouteResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncRouteResourceWithStreamingResponse(self) async def retrieve( @@ -80,7 +103,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, ) -> RouteRetrieveResponse: """ Retrieve information for a specific route identified by its unique ID. @@ -97,7 +120,7 @@ async def retrieve( if not route_id: raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") return await self._get( - f"/api/where/route/{route_id}.json", + path_template("/api/where/route/{route_id}.json", route_id=route_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/onebusaway/resources/route_ids_for_agency.py b/src/onebusaway/resources/route_ids_for_agency.py new file mode 100644 index 00000000..956bca9d --- /dev/null +++ b/src/onebusaway/resources/route_ids_for_agency.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +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 .._base_client import make_request_options +from ..types.route_ids_for_agency_list_response import RouteIDsForAgencyListResponse + +__all__ = ["RouteIDsForAgencyResource", "AsyncRouteIDsForAgencyResource"] + + +class RouteIDsForAgencyResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RouteIDsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return RouteIDsForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RouteIDsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return RouteIDsForAgencyResourceWithStreamingResponse(self) + + def list( + self, + agency_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, + ) -> RouteIDsForAgencyListResponse: + """ + Get route IDs for a specific agency + + 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return self._get( + path_template("/api/where/route-ids-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RouteIDsForAgencyListResponse, + ) + + +class AsyncRouteIDsForAgencyResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRouteIDsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncRouteIDsForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRouteIDsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncRouteIDsForAgencyResourceWithStreamingResponse(self) + + async def list( + self, + agency_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, + ) -> RouteIDsForAgencyListResponse: + """ + Get route IDs for a specific agency + + 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return await self._get( + path_template("/api/where/route-ids-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RouteIDsForAgencyListResponse, + ) + + +class RouteIDsForAgencyResourceWithRawResponse: + def __init__(self, route_ids_for_agency: RouteIDsForAgencyResource) -> None: + self._route_ids_for_agency = route_ids_for_agency + + self.list = to_raw_response_wrapper( + route_ids_for_agency.list, + ) + + +class AsyncRouteIDsForAgencyResourceWithRawResponse: + def __init__(self, route_ids_for_agency: AsyncRouteIDsForAgencyResource) -> None: + self._route_ids_for_agency = route_ids_for_agency + + self.list = async_to_raw_response_wrapper( + route_ids_for_agency.list, + ) + + +class RouteIDsForAgencyResourceWithStreamingResponse: + def __init__(self, route_ids_for_agency: RouteIDsForAgencyResource) -> None: + self._route_ids_for_agency = route_ids_for_agency + + self.list = to_streamed_response_wrapper( + route_ids_for_agency.list, + ) + + +class AsyncRouteIDsForAgencyResourceWithStreamingResponse: + def __init__(self, route_ids_for_agency: AsyncRouteIDsForAgencyResource) -> None: + self._route_ids_for_agency = route_ids_for_agency + + self.list = async_to_streamed_response_wrapper( + route_ids_for_agency.list, + ) diff --git a/src/onebusaway/resources/routes_for_agency.py b/src/onebusaway/resources/routes_for_agency.py new file mode 100644 index 00000000..6845cb84 --- /dev/null +++ b/src/onebusaway/resources/routes_for_agency.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +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 .._base_client import make_request_options +from ..types.routes_for_agency_list_response import RoutesForAgencyListResponse + +__all__ = ["RoutesForAgencyResource", "AsyncRoutesForAgencyResource"] + + +class RoutesForAgencyResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RoutesForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return RoutesForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RoutesForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return RoutesForAgencyResourceWithStreamingResponse(self) + + def list( + self, + agency_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, + ) -> RoutesForAgencyListResponse: + """ + Retrieve the list of all routes for a particular agency by 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return self._get( + path_template("/api/where/routes-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RoutesForAgencyListResponse, + ) + + +class AsyncRoutesForAgencyResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRoutesForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncRoutesForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRoutesForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncRoutesForAgencyResourceWithStreamingResponse(self) + + async def list( + self, + agency_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, + ) -> RoutesForAgencyListResponse: + """ + Retrieve the list of all routes for a particular agency by 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return await self._get( + path_template("/api/where/routes-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RoutesForAgencyListResponse, + ) + + +class RoutesForAgencyResourceWithRawResponse: + def __init__(self, routes_for_agency: RoutesForAgencyResource) -> None: + self._routes_for_agency = routes_for_agency + + self.list = to_raw_response_wrapper( + routes_for_agency.list, + ) + + +class AsyncRoutesForAgencyResourceWithRawResponse: + def __init__(self, routes_for_agency: AsyncRoutesForAgencyResource) -> None: + self._routes_for_agency = routes_for_agency + + self.list = async_to_raw_response_wrapper( + routes_for_agency.list, + ) + + +class RoutesForAgencyResourceWithStreamingResponse: + def __init__(self, routes_for_agency: RoutesForAgencyResource) -> None: + self._routes_for_agency = routes_for_agency + + self.list = to_streamed_response_wrapper( + routes_for_agency.list, + ) + + +class AsyncRoutesForAgencyResourceWithStreamingResponse: + def __init__(self, routes_for_agency: AsyncRoutesForAgencyResource) -> None: + self._routes_for_agency = routes_for_agency + + self.list = async_to_streamed_response_wrapper( + routes_for_agency.list, + ) diff --git a/src/onebusaway/resources/routes_for_location.py b/src/onebusaway/resources/routes_for_location.py new file mode 100644 index 00000000..76e466df --- /dev/null +++ b/src/onebusaway/resources/routes_for_location.py @@ -0,0 +1,199 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import routes_for_location_list_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 .._base_client import make_request_options +from ..types.routes_for_location_list_response import RoutesForLocationListResponse + +__all__ = ["RoutesForLocationResource", "AsyncRoutesForLocationResource"] + + +class RoutesForLocationResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RoutesForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return RoutesForLocationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RoutesForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return RoutesForLocationResourceWithStreamingResponse(self) + + def list( + self, + *, + lat: float, + lon: float, + lat_span: float | Omit = omit, + lon_span: float | Omit = omit, + query: str | Omit = omit, + radius: float | 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, + ) -> RoutesForLocationListResponse: + """ + routes-for-location + + 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 + """ + return self._get( + "/api/where/routes-for-location.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "lat": lat, + "lon": lon, + "lat_span": lat_span, + "lon_span": lon_span, + "query": query, + "radius": radius, + }, + routes_for_location_list_params.RoutesForLocationListParams, + ), + ), + cast_to=RoutesForLocationListResponse, + ) + + +class AsyncRoutesForLocationResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRoutesForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncRoutesForLocationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRoutesForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncRoutesForLocationResourceWithStreamingResponse(self) + + async def list( + self, + *, + lat: float, + lon: float, + lat_span: float | Omit = omit, + lon_span: float | Omit = omit, + query: str | Omit = omit, + radius: float | 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, + ) -> RoutesForLocationListResponse: + """ + routes-for-location + + 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 + """ + return await self._get( + "/api/where/routes-for-location.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "lat": lat, + "lon": lon, + "lat_span": lat_span, + "lon_span": lon_span, + "query": query, + "radius": radius, + }, + routes_for_location_list_params.RoutesForLocationListParams, + ), + ), + cast_to=RoutesForLocationListResponse, + ) + + +class RoutesForLocationResourceWithRawResponse: + def __init__(self, routes_for_location: RoutesForLocationResource) -> None: + self._routes_for_location = routes_for_location + + self.list = to_raw_response_wrapper( + routes_for_location.list, + ) + + +class AsyncRoutesForLocationResourceWithRawResponse: + def __init__(self, routes_for_location: AsyncRoutesForLocationResource) -> None: + self._routes_for_location = routes_for_location + + self.list = async_to_raw_response_wrapper( + routes_for_location.list, + ) + + +class RoutesForLocationResourceWithStreamingResponse: + def __init__(self, routes_for_location: RoutesForLocationResource) -> None: + self._routes_for_location = routes_for_location + + self.list = to_streamed_response_wrapper( + routes_for_location.list, + ) + + +class AsyncRoutesForLocationResourceWithStreamingResponse: + def __init__(self, routes_for_location: AsyncRoutesForLocationResource) -> None: + self._routes_for_location = routes_for_location + + self.list = async_to_streamed_response_wrapper( + routes_for_location.list, + ) diff --git a/src/onebusaway/resources/schedule_for_route.py b/src/onebusaway/resources/schedule_for_route.py new file mode 100644 index 00000000..5df8b5c5 --- /dev/null +++ b/src/onebusaway/resources/schedule_for_route.py @@ -0,0 +1,188 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date + +import httpx + +from ..types import schedule_for_route_retrieve_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, 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 .._base_client import make_request_options +from ..types.schedule_for_route_retrieve_response import ScheduleForRouteRetrieveResponse + +__all__ = ["ScheduleForRouteResource", "AsyncScheduleForRouteResource"] + + +class ScheduleForRouteResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ScheduleForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return ScheduleForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ScheduleForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return ScheduleForRouteResourceWithStreamingResponse(self) + + def retrieve( + self, + route_id: str, + *, + date: Union[str, date] | 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, + ) -> ScheduleForRouteRetrieveResponse: + """ + Retrieve the full schedule for a route on a particular day + + Args: + date: The date for which you want to request a schedule in the format YYYY-MM-DD + (optional, defaults to current date) + + 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 route_id: + raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") + return self._get( + path_template("/api/where/schedule-for-route/{route_id}.json", route_id=route_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"date": date}, schedule_for_route_retrieve_params.ScheduleForRouteRetrieveParams + ), + ), + cast_to=ScheduleForRouteRetrieveResponse, + ) + + +class AsyncScheduleForRouteResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncScheduleForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncScheduleForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncScheduleForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncScheduleForRouteResourceWithStreamingResponse(self) + + async def retrieve( + self, + route_id: str, + *, + date: Union[str, date] | 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, + ) -> ScheduleForRouteRetrieveResponse: + """ + Retrieve the full schedule for a route on a particular day + + Args: + date: The date for which you want to request a schedule in the format YYYY-MM-DD + (optional, defaults to current date) + + 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 route_id: + raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") + return await self._get( + path_template("/api/where/schedule-for-route/{route_id}.json", route_id=route_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"date": date}, schedule_for_route_retrieve_params.ScheduleForRouteRetrieveParams + ), + ), + cast_to=ScheduleForRouteRetrieveResponse, + ) + + +class ScheduleForRouteResourceWithRawResponse: + def __init__(self, schedule_for_route: ScheduleForRouteResource) -> None: + self._schedule_for_route = schedule_for_route + + self.retrieve = to_raw_response_wrapper( + schedule_for_route.retrieve, + ) + + +class AsyncScheduleForRouteResourceWithRawResponse: + def __init__(self, schedule_for_route: AsyncScheduleForRouteResource) -> None: + self._schedule_for_route = schedule_for_route + + self.retrieve = async_to_raw_response_wrapper( + schedule_for_route.retrieve, + ) + + +class ScheduleForRouteResourceWithStreamingResponse: + def __init__(self, schedule_for_route: ScheduleForRouteResource) -> None: + self._schedule_for_route = schedule_for_route + + self.retrieve = to_streamed_response_wrapper( + schedule_for_route.retrieve, + ) + + +class AsyncScheduleForRouteResourceWithStreamingResponse: + def __init__(self, schedule_for_route: AsyncScheduleForRouteResource) -> None: + self._schedule_for_route = schedule_for_route + + self.retrieve = async_to_streamed_response_wrapper( + schedule_for_route.retrieve, + ) diff --git a/src/onebusaway/resources/schedule_for_stop.py b/src/onebusaway/resources/schedule_for_stop.py index 9b449097..894b014f 100644 --- a/src/onebusaway/resources/schedule_for_stop.py +++ b/src/onebusaway/resources/schedule_for_stop.py @@ -8,11 +8,8 @@ import httpx from ..types import schedule_for_stop_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -30,23 +27,34 @@ class ScheduleForStopResource(SyncAPIResource): @cached_property def with_raw_response(self) -> ScheduleForStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return ScheduleForStopResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> ScheduleForStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return ScheduleForStopResourceWithStreamingResponse(self) def retrieve( self, stop_id: str, *, - date: Union[str, date] | NotGiven = NOT_GIVEN, + date: Union[str, date] | 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, ) -> ScheduleForStopRetrieveResponse: """ Get schedule for a specific stop @@ -66,7 +74,7 @@ def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return self._get( - f"/api/where/schedule-for-stop/{stop_id}.json", + path_template("/api/where/schedule-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -81,23 +89,34 @@ def retrieve( class AsyncScheduleForStopResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncScheduleForStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncScheduleForStopResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncScheduleForStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncScheduleForStopResourceWithStreamingResponse(self) async def retrieve( self, stop_id: str, *, - date: Union[str, date] | NotGiven = NOT_GIVEN, + date: Union[str, date] | 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, ) -> ScheduleForStopRetrieveResponse: """ Get schedule for a specific stop @@ -117,7 +136,7 @@ async def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return await self._get( - f"/api/where/schedule-for-stop/{stop_id}.json", + path_template("/api/where/schedule-for-stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/resources/search_for_route.py b/src/onebusaway/resources/search_for_route.py new file mode 100644 index 00000000..4248ca51 --- /dev/null +++ b/src/onebusaway/resources/search_for_route.py @@ -0,0 +1,191 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import search_for_route_list_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 .._base_client import make_request_options +from ..types.search_for_route_list_response import SearchForRouteListResponse + +__all__ = ["SearchForRouteResource", "AsyncSearchForRouteResource"] + + +class SearchForRouteResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SearchForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return SearchForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SearchForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return SearchForRouteResourceWithStreamingResponse(self) + + def list( + self, + *, + input: str, + max_count: 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, + ) -> SearchForRouteListResponse: + """ + Search for a route based on its name. + + Args: + input: The string to search for. + + max_count: The max number of results to return. Defaults to 20. + + 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/where/search/route.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "input": input, + "max_count": max_count, + }, + search_for_route_list_params.SearchForRouteListParams, + ), + ), + cast_to=SearchForRouteListResponse, + ) + + +class AsyncSearchForRouteResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSearchForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncSearchForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSearchForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncSearchForRouteResourceWithStreamingResponse(self) + + async def list( + self, + *, + input: str, + max_count: 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, + ) -> SearchForRouteListResponse: + """ + Search for a route based on its name. + + Args: + input: The string to search for. + + max_count: The max number of results to return. Defaults to 20. + + 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 await self._get( + "/api/where/search/route.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "input": input, + "max_count": max_count, + }, + search_for_route_list_params.SearchForRouteListParams, + ), + ), + cast_to=SearchForRouteListResponse, + ) + + +class SearchForRouteResourceWithRawResponse: + def __init__(self, search_for_route: SearchForRouteResource) -> None: + self._search_for_route = search_for_route + + self.list = to_raw_response_wrapper( + search_for_route.list, + ) + + +class AsyncSearchForRouteResourceWithRawResponse: + def __init__(self, search_for_route: AsyncSearchForRouteResource) -> None: + self._search_for_route = search_for_route + + self.list = async_to_raw_response_wrapper( + search_for_route.list, + ) + + +class SearchForRouteResourceWithStreamingResponse: + def __init__(self, search_for_route: SearchForRouteResource) -> None: + self._search_for_route = search_for_route + + self.list = to_streamed_response_wrapper( + search_for_route.list, + ) + + +class AsyncSearchForRouteResourceWithStreamingResponse: + def __init__(self, search_for_route: AsyncSearchForRouteResource) -> None: + self._search_for_route = search_for_route + + self.list = async_to_streamed_response_wrapper( + search_for_route.list, + ) diff --git a/src/onebusaway/resources/search_for_stop.py b/src/onebusaway/resources/search_for_stop.py new file mode 100644 index 00000000..a95cbdfb --- /dev/null +++ b/src/onebusaway/resources/search_for_stop.py @@ -0,0 +1,191 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import search_for_stop_list_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 .._base_client import make_request_options +from ..types.search_for_stop_list_response import SearchForStopListResponse + +__all__ = ["SearchForStopResource", "AsyncSearchForStopResource"] + + +class SearchForStopResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SearchForStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return SearchForStopResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SearchForStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return SearchForStopResourceWithStreamingResponse(self) + + def list( + self, + *, + input: str, + max_count: 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, + ) -> SearchForStopListResponse: + """ + Search for a stop based on its name. + + Args: + input: The string to search for. + + max_count: The max number of results to return. Defaults to 20. + + 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/where/search/stop.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "input": input, + "max_count": max_count, + }, + search_for_stop_list_params.SearchForStopListParams, + ), + ), + cast_to=SearchForStopListResponse, + ) + + +class AsyncSearchForStopResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSearchForStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncSearchForStopResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSearchForStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncSearchForStopResourceWithStreamingResponse(self) + + async def list( + self, + *, + input: str, + max_count: 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, + ) -> SearchForStopListResponse: + """ + Search for a stop based on its name. + + Args: + input: The string to search for. + + max_count: The max number of results to return. Defaults to 20. + + 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 await self._get( + "/api/where/search/stop.json", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "input": input, + "max_count": max_count, + }, + search_for_stop_list_params.SearchForStopListParams, + ), + ), + cast_to=SearchForStopListResponse, + ) + + +class SearchForStopResourceWithRawResponse: + def __init__(self, search_for_stop: SearchForStopResource) -> None: + self._search_for_stop = search_for_stop + + self.list = to_raw_response_wrapper( + search_for_stop.list, + ) + + +class AsyncSearchForStopResourceWithRawResponse: + def __init__(self, search_for_stop: AsyncSearchForStopResource) -> None: + self._search_for_stop = search_for_stop + + self.list = async_to_raw_response_wrapper( + search_for_stop.list, + ) + + +class SearchForStopResourceWithStreamingResponse: + def __init__(self, search_for_stop: SearchForStopResource) -> None: + self._search_for_stop = search_for_stop + + self.list = to_streamed_response_wrapper( + search_for_stop.list, + ) + + +class AsyncSearchForStopResourceWithStreamingResponse: + def __init__(self, search_for_stop: AsyncSearchForStopResource) -> None: + self._search_for_stop = search_for_stop + + self.list = async_to_streamed_response_wrapper( + search_for_stop.list, + ) diff --git a/src/onebusaway/resources/shape.py b/src/onebusaway/resources/shape.py new file mode 100644 index 00000000..42d75acd --- /dev/null +++ b/src/onebusaway/resources/shape.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +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 .._base_client import make_request_options +from ..types.shape_retrieve_response import ShapeRetrieveResponse + +__all__ = ["ShapeResource", "AsyncShapeResource"] + + +class ShapeResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ShapeResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return ShapeResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ShapeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return ShapeResourceWithStreamingResponse(self) + + def retrieve( + self, + shape_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, + ) -> ShapeRetrieveResponse: + """ + Retrieve a shape (the path traveled by a transit vehicle) by 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 shape_id: + raise ValueError(f"Expected a non-empty value for `shape_id` but received {shape_id!r}") + return self._get( + path_template("/api/where/shape/{shape_id}.json", shape_id=shape_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ShapeRetrieveResponse, + ) + + +class AsyncShapeResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncShapeResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncShapeResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncShapeResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncShapeResourceWithStreamingResponse(self) + + async def retrieve( + self, + shape_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, + ) -> ShapeRetrieveResponse: + """ + Retrieve a shape (the path traveled by a transit vehicle) by 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 shape_id: + raise ValueError(f"Expected a non-empty value for `shape_id` but received {shape_id!r}") + return await self._get( + path_template("/api/where/shape/{shape_id}.json", shape_id=shape_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ShapeRetrieveResponse, + ) + + +class ShapeResourceWithRawResponse: + def __init__(self, shape: ShapeResource) -> None: + self._shape = shape + + self.retrieve = to_raw_response_wrapper( + shape.retrieve, + ) + + +class AsyncShapeResourceWithRawResponse: + def __init__(self, shape: AsyncShapeResource) -> None: + self._shape = shape + + self.retrieve = async_to_raw_response_wrapper( + shape.retrieve, + ) + + +class ShapeResourceWithStreamingResponse: + def __init__(self, shape: ShapeResource) -> None: + self._shape = shape + + self.retrieve = to_streamed_response_wrapper( + shape.retrieve, + ) + + +class AsyncShapeResourceWithStreamingResponse: + def __init__(self, shape: AsyncShapeResource) -> None: + self._shape = shape + + self.retrieve = async_to_streamed_response_wrapper( + shape.retrieve, + ) diff --git a/src/onebusaway/resources/stop.py b/src/onebusaway/resources/stop.py index 10b95ec2..9efd7f25 100644 --- a/src/onebusaway/resources/stop.py +++ b/src/onebusaway/resources/stop.py @@ -4,7 +4,8 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,10 +23,21 @@ class StopResource(SyncAPIResource): @cached_property def with_raw_response(self) -> StopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return StopResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> StopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return StopResourceWithStreamingResponse(self) def retrieve( @@ -37,7 +49,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, ) -> StopRetrieveResponse: """ Get details of a specific stop @@ -54,7 +66,7 @@ def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return self._get( - f"/api/where/stop/{stop_id}.json", + path_template("/api/where/stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,10 +77,21 @@ def retrieve( class AsyncStopResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncStopResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncStopResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncStopResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncStopResourceWithStreamingResponse(self) async def retrieve( @@ -80,7 +103,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, ) -> StopRetrieveResponse: """ Get details of a specific stop @@ -97,7 +120,7 @@ async def retrieve( if not stop_id: raise ValueError(f"Expected a non-empty value for `stop_id` but received {stop_id!r}") return await self._get( - f"/api/where/stop/{stop_id}.json", + path_template("/api/where/stop/{stop_id}.json", stop_id=stop_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/onebusaway/resources/stop_ids_for_agency.py b/src/onebusaway/resources/stop_ids_for_agency.py index a611a6e8..51270222 100644 --- a/src/onebusaway/resources/stop_ids_for_agency.py +++ b/src/onebusaway/resources/stop_ids_for_agency.py @@ -4,7 +4,8 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,10 +23,21 @@ class StopIDsForAgencyResource(SyncAPIResource): @cached_property def with_raw_response(self) -> StopIDsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return StopIDsForAgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> StopIDsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return StopIDsForAgencyResourceWithStreamingResponse(self) def list( @@ -37,7 +49,7 @@ def list( 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, ) -> StopIDsForAgencyListResponse: """ Get stop IDs for a specific agency @@ -54,7 +66,7 @@ def list( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return self._get( - f"/api/where/stop-ids-for-agency/{agency_id}.json", + path_template("/api/where/stop-ids-for-agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,10 +77,21 @@ def list( class AsyncStopIDsForAgencyResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncStopIDsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncStopIDsForAgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncStopIDsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncStopIDsForAgencyResourceWithStreamingResponse(self) async def list( @@ -80,7 +103,7 @@ async def list( 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, ) -> StopIDsForAgencyListResponse: """ Get stop IDs for a specific agency @@ -97,7 +120,7 @@ async def list( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return await self._get( - f"/api/where/stop-ids-for-agency/{agency_id}.json", + path_template("/api/where/stop-ids-for-agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/onebusaway/resources/stops_for_agency.py b/src/onebusaway/resources/stops_for_agency.py new file mode 100644 index 00000000..70f55970 --- /dev/null +++ b/src/onebusaway/resources/stops_for_agency.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +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 .._base_client import make_request_options +from ..types.stops_for_agency_list_response import StopsForAgencyListResponse + +__all__ = ["StopsForAgencyResource", "AsyncStopsForAgencyResource"] + + +class StopsForAgencyResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> StopsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return StopsForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> StopsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return StopsForAgencyResourceWithStreamingResponse(self) + + def list( + self, + agency_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, + ) -> StopsForAgencyListResponse: + """ + Get stops for a specific agency + + 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return self._get( + path_template("/api/where/stops-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StopsForAgencyListResponse, + ) + + +class AsyncStopsForAgencyResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncStopsForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncStopsForAgencyResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncStopsForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncStopsForAgencyResourceWithStreamingResponse(self) + + async def list( + self, + agency_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, + ) -> StopsForAgencyListResponse: + """ + Get stops for a specific agency + + 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 agency_id: + raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") + return await self._get( + path_template("/api/where/stops-for-agency/{agency_id}.json", agency_id=agency_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=StopsForAgencyListResponse, + ) + + +class StopsForAgencyResourceWithRawResponse: + def __init__(self, stops_for_agency: StopsForAgencyResource) -> None: + self._stops_for_agency = stops_for_agency + + self.list = to_raw_response_wrapper( + stops_for_agency.list, + ) + + +class AsyncStopsForAgencyResourceWithRawResponse: + def __init__(self, stops_for_agency: AsyncStopsForAgencyResource) -> None: + self._stops_for_agency = stops_for_agency + + self.list = async_to_raw_response_wrapper( + stops_for_agency.list, + ) + + +class StopsForAgencyResourceWithStreamingResponse: + def __init__(self, stops_for_agency: StopsForAgencyResource) -> None: + self._stops_for_agency = stops_for_agency + + self.list = to_streamed_response_wrapper( + stops_for_agency.list, + ) + + +class AsyncStopsForAgencyResourceWithStreamingResponse: + def __init__(self, stops_for_agency: AsyncStopsForAgencyResource) -> None: + self._stops_for_agency = stops_for_agency + + self.list = async_to_streamed_response_wrapper( + stops_for_agency.list, + ) diff --git a/src/onebusaway/resources/stops_for_location.py b/src/onebusaway/resources/stops_for_location.py index dd64b27b..ea17f450 100644 --- a/src/onebusaway/resources/stops_for_location.py +++ b/src/onebusaway/resources/stops_for_location.py @@ -4,12 +4,9 @@ import httpx -from ..types import stops_for_location_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..types import stops_for_location_list_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 ( @@ -19,7 +16,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.stops_for_location_retrieve_response import StopsForLocationRetrieveResponse +from ..types.stops_for_location_list_response import StopsForLocationListResponse __all__ = ["StopsForLocationResource", "AsyncStopsForLocationResource"] @@ -27,28 +24,51 @@ class StopsForLocationResource(SyncAPIResource): @cached_property def with_raw_response(self) -> StopsForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return StopsForLocationResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> StopsForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return StopsForLocationResourceWithStreamingResponse(self) - def retrieve( + def list( self, *, - lat: float | NotGiven = NOT_GIVEN, - lon: float | NotGiven = NOT_GIVEN, + lat: float, + lon: float, + lat_span: float | Omit = omit, + lon_span: float | Omit = omit, + query: str | Omit = omit, + radius: float | 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, - ) -> StopsForLocationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StopsForLocationListResponse: """ stops-for-location Args: + lat_span: An alternative to radius to set the search bounding box (optional) + + lon_span: An alternative to radius to set the search bounding box (optional) + + query: A search query string to filter the results + + radius: The radius in meters to search within + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -68,39 +88,66 @@ def retrieve( { "lat": lat, "lon": lon, + "lat_span": lat_span, + "lon_span": lon_span, + "query": query, + "radius": radius, }, - stops_for_location_retrieve_params.StopsForLocationRetrieveParams, + stops_for_location_list_params.StopsForLocationListParams, ), ), - cast_to=StopsForLocationRetrieveResponse, + cast_to=StopsForLocationListResponse, ) class AsyncStopsForLocationResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncStopsForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncStopsForLocationResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncStopsForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncStopsForLocationResourceWithStreamingResponse(self) - async def retrieve( + async def list( self, *, - lat: float | NotGiven = NOT_GIVEN, - lon: float | NotGiven = NOT_GIVEN, + lat: float, + lon: float, + lat_span: float | Omit = omit, + lon_span: float | Omit = omit, + query: str | Omit = omit, + radius: float | 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, - ) -> StopsForLocationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> StopsForLocationListResponse: """ stops-for-location Args: + lat_span: An alternative to radius to set the search bounding box (optional) + + lon_span: An alternative to radius to set the search bounding box (optional) + + query: A search query string to filter the results + + radius: The radius in meters to search within + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -120,11 +167,15 @@ async def retrieve( { "lat": lat, "lon": lon, + "lat_span": lat_span, + "lon_span": lon_span, + "query": query, + "radius": radius, }, - stops_for_location_retrieve_params.StopsForLocationRetrieveParams, + stops_for_location_list_params.StopsForLocationListParams, ), ), - cast_to=StopsForLocationRetrieveResponse, + cast_to=StopsForLocationListResponse, ) @@ -132,8 +183,8 @@ class StopsForLocationResourceWithRawResponse: def __init__(self, stops_for_location: StopsForLocationResource) -> None: self._stops_for_location = stops_for_location - self.retrieve = to_raw_response_wrapper( - stops_for_location.retrieve, + self.list = to_raw_response_wrapper( + stops_for_location.list, ) @@ -141,8 +192,8 @@ class AsyncStopsForLocationResourceWithRawResponse: def __init__(self, stops_for_location: AsyncStopsForLocationResource) -> None: self._stops_for_location = stops_for_location - self.retrieve = async_to_raw_response_wrapper( - stops_for_location.retrieve, + self.list = async_to_raw_response_wrapper( + stops_for_location.list, ) @@ -150,8 +201,8 @@ class StopsForLocationResourceWithStreamingResponse: def __init__(self, stops_for_location: StopsForLocationResource) -> None: self._stops_for_location = stops_for_location - self.retrieve = to_streamed_response_wrapper( - stops_for_location.retrieve, + self.list = to_streamed_response_wrapper( + stops_for_location.list, ) @@ -159,6 +210,6 @@ class AsyncStopsForLocationResourceWithStreamingResponse: def __init__(self, stops_for_location: AsyncStopsForLocationResource) -> None: self._stops_for_location = stops_for_location - self.retrieve = async_to_streamed_response_wrapper( - stops_for_location.retrieve, + self.list = async_to_streamed_response_wrapper( + stops_for_location.list, ) diff --git a/src/onebusaway/resources/stops_for_route.py b/src/onebusaway/resources/stops_for_route.py index 2a8e5b83..1540baa9 100644 --- a/src/onebusaway/resources/stops_for_route.py +++ b/src/onebusaway/resources/stops_for_route.py @@ -5,11 +5,8 @@ import httpx from ..types import stops_for_route_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -27,24 +24,35 @@ class StopsForRouteResource(SyncAPIResource): @cached_property def with_raw_response(self) -> StopsForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return StopsForRouteResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> StopsForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return StopsForRouteResourceWithStreamingResponse(self) def list( self, route_id: str, *, - include_polylines: bool | NotGiven = NOT_GIVEN, - time: str | NotGiven = NOT_GIVEN, + include_polylines: bool | Omit = omit, + time: 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, ) -> StopsForRouteListResponse: """ Get stops for a specific route @@ -65,7 +73,7 @@ def list( if not route_id: raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") return self._get( - f"/api/where/stops-for-route/{route_id}.json", + path_template("/api/where/stops-for-route/{route_id}.json", route_id=route_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -86,24 +94,35 @@ def list( class AsyncStopsForRouteResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncStopsForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncStopsForRouteResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncStopsForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncStopsForRouteResourceWithStreamingResponse(self) async def list( self, route_id: str, *, - include_polylines: bool | NotGiven = NOT_GIVEN, - time: str | NotGiven = NOT_GIVEN, + include_polylines: bool | Omit = omit, + time: 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, ) -> StopsForRouteListResponse: """ Get stops for a specific route @@ -124,7 +143,7 @@ async def list( if not route_id: raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") return await self._get( - f"/api/where/stops-for-route/{route_id}.json", + path_template("/api/where/stops-for-route/{route_id}.json", route_id=route_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/resources/trip.py b/src/onebusaway/resources/trip.py index d4965a67..4fd00046 100644 --- a/src/onebusaway/resources/trip.py +++ b/src/onebusaway/resources/trip.py @@ -4,7 +4,8 @@ import httpx -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -22,10 +23,21 @@ class TripResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TripResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return TripResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> TripResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return TripResourceWithStreamingResponse(self) def retrieve( @@ -37,7 +49,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, ) -> TripRetrieveResponse: """ Get details of a specific trip @@ -54,7 +66,7 @@ def retrieve( if not trip_id: raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") return self._get( - f"/api/where/trip/{trip_id}.json", + path_template("/api/where/trip/{trip_id}.json", trip_id=trip_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -65,10 +77,21 @@ def retrieve( class AsyncTripResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTripResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncTripResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTripResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncTripResourceWithStreamingResponse(self) async def retrieve( @@ -80,7 +103,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, ) -> TripRetrieveResponse: """ Get details of a specific trip @@ -97,7 +120,7 @@ async def retrieve( if not trip_id: raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") return await self._get( - f"/api/where/trip/{trip_id}.json", + path_template("/api/where/trip/{trip_id}.json", trip_id=trip_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/onebusaway/resources/trip_details.py b/src/onebusaway/resources/trip_details.py index 7245d2b3..a2728d6a 100644 --- a/src/onebusaway/resources/trip_details.py +++ b/src/onebusaway/resources/trip_details.py @@ -5,11 +5,8 @@ import httpx from ..types import trip_detail_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -27,27 +24,38 @@ class TripDetailsResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TripDetailsResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return TripDetailsResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> TripDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return TripDetailsResourceWithStreamingResponse(self) def retrieve( self, trip_id: str, *, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_status: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - service_date: int | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + include_trip: bool | Omit = omit, + service_date: int | Omit = omit, + time: 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, ) -> TripDetailRetrieveResponse: """ Retrieve Trip Details @@ -77,7 +85,7 @@ def retrieve( if not trip_id: raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") return self._get( - f"/api/where/trip-details/{trip_id}.json", + path_template("/api/where/trip-details/{trip_id}.json", trip_id=trip_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -101,27 +109,38 @@ def retrieve( class AsyncTripDetailsResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTripDetailsResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncTripDetailsResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTripDetailsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncTripDetailsResourceWithStreamingResponse(self) async def retrieve( self, trip_id: str, *, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_status: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - service_date: int | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + include_trip: bool | Omit = omit, + service_date: int | Omit = omit, + time: 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, ) -> TripDetailRetrieveResponse: """ Retrieve Trip Details @@ -151,7 +170,7 @@ async def retrieve( if not trip_id: raise ValueError(f"Expected a non-empty value for `trip_id` but received {trip_id!r}") return await self._get( - f"/api/where/trip-details/{trip_id}.json", + path_template("/api/where/trip-details/{trip_id}.json", trip_id=trip_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/resources/trip_for_vehicle.py b/src/onebusaway/resources/trip_for_vehicle.py index b98c3edd..cac78972 100644 --- a/src/onebusaway/resources/trip_for_vehicle.py +++ b/src/onebusaway/resources/trip_for_vehicle.py @@ -5,11 +5,8 @@ import httpx from ..types import trip_for_vehicle_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -27,26 +24,37 @@ class TripForVehicleResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TripForVehicleResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return TripForVehicleResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> TripForVehicleResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return TripForVehicleResourceWithStreamingResponse(self) def retrieve( self, vehicle_id: str, *, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_status: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + include_trip: bool | Omit = omit, + time: 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, ) -> TripForVehicleRetrieveResponse: """ Retrieve trip for a specific vehicle @@ -74,7 +82,7 @@ def retrieve( if not vehicle_id: raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}") return self._get( - f"/api/where/trip-for-vehicle/{vehicle_id}.json", + path_template("/api/where/trip-for-vehicle/{vehicle_id}.json", vehicle_id=vehicle_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -97,26 +105,37 @@ def retrieve( class AsyncTripForVehicleResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTripForVehicleResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncTripForVehicleResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTripForVehicleResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncTripForVehicleResourceWithStreamingResponse(self) async def retrieve( self, vehicle_id: str, *, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_status: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + include_trip: bool | Omit = omit, + time: 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, ) -> TripForVehicleRetrieveResponse: """ Retrieve trip for a specific vehicle @@ -144,7 +163,7 @@ async def retrieve( if not vehicle_id: raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}") return await self._get( - f"/api/where/trip-for-vehicle/{vehicle_id}.json", + path_template("/api/where/trip-for-vehicle/{vehicle_id}.json", vehicle_id=vehicle_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/resources/trips_for_location.py b/src/onebusaway/resources/trips_for_location.py index 947283db..19b13dbd 100644 --- a/src/onebusaway/resources/trips_for_location.py +++ b/src/onebusaway/resources/trips_for_location.py @@ -4,12 +4,9 @@ import httpx -from ..types import trips_for_location_retrieve_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from ..types import trips_for_location_list_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 ( @@ -19,7 +16,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.trips_for_location_retrieve_response import TripsForLocationRetrieveResponse +from ..types.trips_for_location_list_response import TripsForLocationListResponse __all__ = ["TripsForLocationResource", "AsyncTripsForLocationResource"] @@ -27,29 +24,40 @@ class TripsForLocationResource(SyncAPIResource): @cached_property def with_raw_response(self) -> TripsForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return TripsForLocationResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> TripsForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return TripsForLocationResourceWithStreamingResponse(self) - def retrieve( + def list( self, *, lat: float, lat_span: float, lon: float, lon_span: float, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_trip: bool | Omit = omit, + time: 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, - ) -> TripsForLocationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TripsForLocationListResponse: """ Retrieve trips for a given location @@ -95,39 +103,50 @@ def retrieve( "include_trip": include_trip, "time": time, }, - trips_for_location_retrieve_params.TripsForLocationRetrieveParams, + trips_for_location_list_params.TripsForLocationListParams, ), ), - cast_to=TripsForLocationRetrieveResponse, + cast_to=TripsForLocationListResponse, ) class AsyncTripsForLocationResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncTripsForLocationResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncTripsForLocationResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncTripsForLocationResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncTripsForLocationResourceWithStreamingResponse(self) - async def retrieve( + async def list( self, *, lat: float, lat_span: float, lon: float, lon_span: float, - include_schedule: bool | NotGiven = NOT_GIVEN, - include_trip: bool | NotGiven = NOT_GIVEN, - time: int | NotGiven = NOT_GIVEN, + include_schedule: bool | Omit = omit, + include_trip: bool | Omit = omit, + time: 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, - ) -> TripsForLocationRetrieveResponse: + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TripsForLocationListResponse: """ Retrieve trips for a given location @@ -173,10 +192,10 @@ async def retrieve( "include_trip": include_trip, "time": time, }, - trips_for_location_retrieve_params.TripsForLocationRetrieveParams, + trips_for_location_list_params.TripsForLocationListParams, ), ), - cast_to=TripsForLocationRetrieveResponse, + cast_to=TripsForLocationListResponse, ) @@ -184,8 +203,8 @@ class TripsForLocationResourceWithRawResponse: def __init__(self, trips_for_location: TripsForLocationResource) -> None: self._trips_for_location = trips_for_location - self.retrieve = to_raw_response_wrapper( - trips_for_location.retrieve, + self.list = to_raw_response_wrapper( + trips_for_location.list, ) @@ -193,8 +212,8 @@ class AsyncTripsForLocationResourceWithRawResponse: def __init__(self, trips_for_location: AsyncTripsForLocationResource) -> None: self._trips_for_location = trips_for_location - self.retrieve = async_to_raw_response_wrapper( - trips_for_location.retrieve, + self.list = async_to_raw_response_wrapper( + trips_for_location.list, ) @@ -202,8 +221,8 @@ class TripsForLocationResourceWithStreamingResponse: def __init__(self, trips_for_location: TripsForLocationResource) -> None: self._trips_for_location = trips_for_location - self.retrieve = to_streamed_response_wrapper( - trips_for_location.retrieve, + self.list = to_streamed_response_wrapper( + trips_for_location.list, ) @@ -211,6 +230,6 @@ class AsyncTripsForLocationResourceWithStreamingResponse: def __init__(self, trips_for_location: AsyncTripsForLocationResource) -> None: self._trips_for_location = trips_for_location - self.retrieve = async_to_streamed_response_wrapper( - trips_for_location.retrieve, + self.list = async_to_streamed_response_wrapper( + trips_for_location.list, ) diff --git a/src/onebusaway/resources/trips_for_route.py b/src/onebusaway/resources/trips_for_route.py new file mode 100644 index 00000000..ed7840dd --- /dev/null +++ b/src/onebusaway/resources/trips_for_route.py @@ -0,0 +1,207 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import trips_for_route_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, 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 .._base_client import make_request_options +from ..types.trips_for_route_list_response import TripsForRouteListResponse + +__all__ = ["TripsForRouteResource", "AsyncTripsForRouteResource"] + + +class TripsForRouteResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TripsForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return TripsForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TripsForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return TripsForRouteResourceWithStreamingResponse(self) + + def list( + self, + route_id: str, + *, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + time: 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, + ) -> TripsForRouteListResponse: + """ + Search for active trips for a specific route. + + Args: + include_schedule: Determine whether full schedule elements are included. Defaults to false. + + include_status: Determine whether full tripStatus elements with real-time information are + included. Defaults to false. + + time: Query the system at a specific time. Useful for testing. + + 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 route_id: + raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") + return self._get( + path_template("/api/where/trips-for-route/{route_id}.json", route_id=route_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_schedule": include_schedule, + "include_status": include_status, + "time": time, + }, + trips_for_route_list_params.TripsForRouteListParams, + ), + ), + cast_to=TripsForRouteListResponse, + ) + + +class AsyncTripsForRouteResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTripsForRouteResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncTripsForRouteResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTripsForRouteResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ + return AsyncTripsForRouteResourceWithStreamingResponse(self) + + async def list( + self, + route_id: str, + *, + include_schedule: bool | Omit = omit, + include_status: bool | Omit = omit, + time: 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, + ) -> TripsForRouteListResponse: + """ + Search for active trips for a specific route. + + Args: + include_schedule: Determine whether full schedule elements are included. Defaults to false. + + include_status: Determine whether full tripStatus elements with real-time information are + included. Defaults to false. + + time: Query the system at a specific time. Useful for testing. + + 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 route_id: + raise ValueError(f"Expected a non-empty value for `route_id` but received {route_id!r}") + return await self._get( + path_template("/api/where/trips-for-route/{route_id}.json", route_id=route_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "include_schedule": include_schedule, + "include_status": include_status, + "time": time, + }, + trips_for_route_list_params.TripsForRouteListParams, + ), + ), + cast_to=TripsForRouteListResponse, + ) + + +class TripsForRouteResourceWithRawResponse: + def __init__(self, trips_for_route: TripsForRouteResource) -> None: + self._trips_for_route = trips_for_route + + self.list = to_raw_response_wrapper( + trips_for_route.list, + ) + + +class AsyncTripsForRouteResourceWithRawResponse: + def __init__(self, trips_for_route: AsyncTripsForRouteResource) -> None: + self._trips_for_route = trips_for_route + + self.list = async_to_raw_response_wrapper( + trips_for_route.list, + ) + + +class TripsForRouteResourceWithStreamingResponse: + def __init__(self, trips_for_route: TripsForRouteResource) -> None: + self._trips_for_route = trips_for_route + + self.list = to_streamed_response_wrapper( + trips_for_route.list, + ) + + +class AsyncTripsForRouteResourceWithStreamingResponse: + def __init__(self, trips_for_route: AsyncTripsForRouteResource) -> None: + self._trips_for_route = trips_for_route + + self.list = async_to_streamed_response_wrapper( + trips_for_route.list, + ) diff --git a/src/onebusaway/resources/vehicles_for_agency.py b/src/onebusaway/resources/vehicles_for_agency.py index 800559be..80c6fd24 100644 --- a/src/onebusaway/resources/vehicles_for_agency.py +++ b/src/onebusaway/resources/vehicles_for_agency.py @@ -5,11 +5,8 @@ import httpx from ..types import vehicles_for_agency_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import ( - maybe_transform, - async_maybe_transform, -) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -27,23 +24,34 @@ class VehiclesForAgencyResource(SyncAPIResource): @cached_property def with_raw_response(self) -> VehiclesForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return VehiclesForAgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> VehiclesForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return VehiclesForAgencyResourceWithStreamingResponse(self) def list( self, agency_id: str, *, - time: str | NotGiven = NOT_GIVEN, + time: 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, ) -> VehiclesForAgencyListResponse: """ Get vehicles for a specific agency @@ -62,7 +70,7 @@ def list( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return self._get( - f"/api/where/vehicles-for-agency/{agency_id}.json", + path_template("/api/where/vehicles-for-agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -77,23 +85,34 @@ def list( class AsyncVehiclesForAgencyResource(AsyncAPIResource): @cached_property def with_raw_response(self) -> AsyncVehiclesForAgencyResourceWithRawResponse: + """ + 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/OneBusAway/python-sdk#accessing-raw-response-data-eg-headers + """ return AsyncVehiclesForAgencyResourceWithRawResponse(self) @cached_property def with_streaming_response(self) -> AsyncVehiclesForAgencyResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/OneBusAway/python-sdk#with_streaming_response + """ return AsyncVehiclesForAgencyResourceWithStreamingResponse(self) async def list( self, agency_id: str, *, - time: str | NotGiven = NOT_GIVEN, + time: 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, ) -> VehiclesForAgencyListResponse: """ Get vehicles for a specific agency @@ -112,7 +131,7 @@ async def list( if not agency_id: raise ValueError(f"Expected a non-empty value for `agency_id` but received {agency_id!r}") return await self._get( - f"/api/where/vehicles-for-agency/{agency_id}.json", + path_template("/api/where/vehicles-for-agency/{agency_id}.json", agency_id=agency_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/onebusaway/types/__init__.py b/src/onebusaway/types/__init__.py index 87b7d0cc..a4bb9969 100644 --- a/src/onebusaway/types/__init__.py +++ b/src/onebusaway/types/__init__.py @@ -5,33 +5,52 @@ from .shared import References as References, ResponseWrapper as ResponseWrapper from .stop_retrieve_response import StopRetrieveResponse as StopRetrieveResponse from .trip_retrieve_response import TripRetrieveResponse as TripRetrieveResponse +from .block_retrieve_response import BlockRetrieveResponse as BlockRetrieveResponse from .route_retrieve_response import RouteRetrieveResponse as RouteRetrieveResponse +from .shape_retrieve_response import ShapeRetrieveResponse as ShapeRetrieveResponse from .agency_retrieve_response import AgencyRetrieveResponse as AgencyRetrieveResponse from .config_retrieve_response import ConfigRetrieveResponse as ConfigRetrieveResponse +from .search_for_stop_list_params import SearchForStopListParams as SearchForStopListParams from .stops_for_route_list_params import StopsForRouteListParams as StopsForRouteListParams from .trip_detail_retrieve_params import TripDetailRetrieveParams as TripDetailRetrieveParams +from .trips_for_route_list_params import TripsForRouteListParams as TripsForRouteListParams +from .search_for_route_list_params import SearchForRouteListParams as SearchForRouteListParams +from .search_for_stop_list_response import SearchForStopListResponse as SearchForStopListResponse from .stops_for_route_list_response import StopsForRouteListResponse as StopsForRouteListResponse from .trip_detail_retrieve_response import TripDetailRetrieveResponse as TripDetailRetrieveResponse +from .trips_for_route_list_response import TripsForRouteListResponse as TripsForRouteListResponse from .current_time_retrieve_response import CurrentTimeRetrieveResponse as CurrentTimeRetrieveResponse +from .search_for_route_list_response import SearchForRouteListResponse as SearchForRouteListResponse +from .stops_for_agency_list_response import StopsForAgencyListResponse as StopsForAgencyListResponse +from .stops_for_location_list_params import StopsForLocationListParams as StopsForLocationListParams +from .trips_for_location_list_params import TripsForLocationListParams as TripsForLocationListParams +from .routes_for_agency_list_response import RoutesForAgencyListResponse as RoutesForAgencyListResponse +from .routes_for_location_list_params import RoutesForLocationListParams as RoutesForLocationListParams from .vehicles_for_agency_list_params import VehiclesForAgencyListParams as VehiclesForAgencyListParams +from .stops_for_location_list_response import StopsForLocationListResponse as StopsForLocationListResponse from .trip_for_vehicle_retrieve_params import TripForVehicleRetrieveParams as TripForVehicleRetrieveParams +from .trips_for_location_list_response import TripsForLocationListResponse as TripsForLocationListResponse from .arrival_and_departure_list_params import ArrivalAndDepartureListParams as ArrivalAndDepartureListParams +from .routes_for_location_list_response import RoutesForLocationListResponse as RoutesForLocationListResponse from .schedule_for_stop_retrieve_params import ScheduleForStopRetrieveParams as ScheduleForStopRetrieveParams from .stop_ids_for_agency_list_response import StopIDsForAgencyListResponse as StopIDsForAgencyListResponse from .vehicles_for_agency_list_response import VehiclesForAgencyListResponse as VehiclesForAgencyListResponse -from .stops_for_location_retrieve_params import StopsForLocationRetrieveParams as StopsForLocationRetrieveParams +from .route_ids_for_agency_list_response import RouteIDsForAgencyListResponse as RouteIDsForAgencyListResponse +from .schedule_for_route_retrieve_params import ScheduleForRouteRetrieveParams as ScheduleForRouteRetrieveParams from .trip_for_vehicle_retrieve_response import TripForVehicleRetrieveResponse as TripForVehicleRetrieveResponse -from .trips_for_location_retrieve_params import TripsForLocationRetrieveParams as TripsForLocationRetrieveParams from .arrival_and_departure_list_response import ArrivalAndDepartureListResponse as ArrivalAndDepartureListResponse from .schedule_for_stop_retrieve_response import ScheduleForStopRetrieveResponse as ScheduleForStopRetrieveResponse -from .stops_for_location_retrieve_response import StopsForLocationRetrieveResponse as StopsForLocationRetrieveResponse -from .trips_for_location_retrieve_response import TripsForLocationRetrieveResponse as TripsForLocationRetrieveResponse +from .agencies_with_coverage_list_response import AgenciesWithCoverageListResponse as AgenciesWithCoverageListResponse +from .schedule_for_route_retrieve_response import ScheduleForRouteRetrieveResponse as ScheduleForRouteRetrieveResponse from .arrival_and_departure_retrieve_params import ( ArrivalAndDepartureRetrieveParams as ArrivalAndDepartureRetrieveParams, ) from .arrival_and_departure_retrieve_response import ( ArrivalAndDepartureRetrieveResponse as ArrivalAndDepartureRetrieveResponse, ) -from .agencies_with_coverage_retrieve_response import ( - AgenciesWithCoverageRetrieveResponse as AgenciesWithCoverageRetrieveResponse, +from .report_problem_with_stop_retrieve_params import ( + ReportProblemWithStopRetrieveParams as ReportProblemWithStopRetrieveParams, +) +from .report_problem_with_trip_retrieve_params import ( + ReportProblemWithTripRetrieveParams as ReportProblemWithTripRetrieveParams, ) diff --git a/src/onebusaway/types/agencies_with_coverage_retrieve_response.py b/src/onebusaway/types/agencies_with_coverage_list_response.py similarity index 57% rename from src/onebusaway/types/agencies_with_coverage_retrieve_response.py rename to src/onebusaway/types/agencies_with_coverage_list_response.py index 6a54093f..27082f99 100644 --- a/src/onebusaway/types/agencies_with_coverage_retrieve_response.py +++ b/src/onebusaway/types/agencies_with_coverage_list_response.py @@ -9,13 +9,13 @@ from .shared.response_wrapper import ResponseWrapper __all__ = [ - "AgenciesWithCoverageRetrieveResponse", - "AgenciesWithCoverageRetrieveResponseData", - "AgenciesWithCoverageRetrieveResponseDataList", + "AgenciesWithCoverageListResponse", + "AgenciesWithCoverageListResponseData", + "AgenciesWithCoverageListResponseDataList", ] -class AgenciesWithCoverageRetrieveResponseDataList(BaseModel): +class AgenciesWithCoverageListResponseDataList(BaseModel): agency_id: str = FieldInfo(alias="agencyId") lat: float @@ -27,13 +27,13 @@ class AgenciesWithCoverageRetrieveResponseDataList(BaseModel): lon_span: float = FieldInfo(alias="lonSpan") -class AgenciesWithCoverageRetrieveResponseData(BaseModel): - list: List[AgenciesWithCoverageRetrieveResponseDataList] +class AgenciesWithCoverageListResponseData(BaseModel): + list: List[AgenciesWithCoverageListResponseDataList] references: References limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) -class AgenciesWithCoverageRetrieveResponse(ResponseWrapper): - data: Optional[AgenciesWithCoverageRetrieveResponseData] = None +class AgenciesWithCoverageListResponse(ResponseWrapper): + data: AgenciesWithCoverageListResponseData diff --git a/src/onebusaway/types/agency_retrieve_response.py b/src/onebusaway/types/agency_retrieve_response.py index bec686ce..61e1bc6b 100644 --- a/src/onebusaway/types/agency_retrieve_response.py +++ b/src/onebusaway/types/agency_retrieve_response.py @@ -38,8 +38,6 @@ class AgencyRetrieveResponseData(BaseModel): references: References - limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) - class AgencyRetrieveResponse(ResponseWrapper): - data: Optional[AgencyRetrieveResponseData] = None + data: AgencyRetrieveResponseData diff --git a/src/onebusaway/types/arrival_and_departure_list_response.py b/src/onebusaway/types/arrival_and_departure_list_response.py index 84eb7677..4da3e86c 100644 --- a/src/onebusaway/types/arrival_and_departure_list_response.py +++ b/src/onebusaway/types/arrival_and_departure_list_response.py @@ -20,6 +20,8 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + lat: Optional[float] = None """Latitude of the last known location of the transit vehicle.""" @@ -28,6 +30,8 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusLast class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + lat: Optional[float] = None """Latitude of the current position of the transit vehicle.""" @@ -36,47 +40,79 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusPosi class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatus(BaseModel): - active_trip_id: Optional[str] = FieldInfo(alias="activeTripId", default=None) + """Trip-specific status for the arriving transit vehicle.""" + + active_trip_id: str = FieldInfo(alias="activeTripId") """Trip ID of the trip the vehicle is actively serving.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of the active trip into the sequence of trips for the active block.""" - closest_stop: Optional[str] = FieldInfo(alias="closestStop", default=None) + closest_stop: str = FieldInfo(alias="closestStop") """ID of the closest stop to the current location of the transit vehicle.""" + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) """ Time offset from the closest stop to the current position of the transit vehicle (in seconds). """ - distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) - """Distance, in meters, the transit vehicle has progressed along the active trip.""" - frequency: Optional[str] = None """Information about frequency-based scheduling, if applicable to the trip.""" - last_known_distance_along_trip: Optional[float] = FieldInfo(alias="lastKnownDistanceAlongTrip", default=None) - """ - Last known distance along the trip received in real-time from the transit - vehicle. - """ - last_known_location: Optional[ ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusLastKnownLocation ] = FieldInfo(alias="lastKnownLocation", default=None) - """Last known location of the transit vehicle.""" + """Last known location of the transit vehicle (optional).""" last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) """Last known orientation value received in real-time from the transit vehicle.""" - last_location_update_time: Optional[int] = FieldInfo(alias="lastLocationUpdateTime", default=None) - """Timestamp of the last known real-time location update from the transit vehicle.""" - - last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) - """Timestamp of the last known real-time update from the transit vehicle.""" - next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) """ID of the next stop the transit vehicle is scheduled to arrive at.""" @@ -86,68 +122,95 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatus(Bas (in seconds). """ - occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) - """Capacity of the transit vehicle in terms of occupancy.""" - - occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) - """Current count of occupants in the transit vehicle.""" - - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) - """Current occupancy status of the transit vehicle.""" - orientation: Optional[float] = None """Orientation of the transit vehicle, represented as an angle in degrees.""" - phase: Optional[str] = None - """Current journey phase of the trip.""" - position: Optional[ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatusPosition] = None """Current position of the transit vehicle.""" - predicted: Optional[bool] = None - """Indicates if real-time arrival info is available for this trip.""" - scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) """ Distance, in meters, the transit vehicle is scheduled to have progressed along the active trip. """ - schedule_deviation: Optional[int] = FieldInfo(alias="scheduleDeviation", default=None) - """Deviation from the schedule in seconds (positive for late, negative for early).""" - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this trip.""" - status: Optional[str] = None - """Current status modifiers for the trip.""" - - total_distance_along_trip: Optional[float] = FieldInfo(alias="totalDistanceAlongTrip", default=None) - """Total length of the trip, in meters.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) """ID of the transit vehicle currently serving the trip.""" class ArrivalAndDepartureListResponseDataEntryArrivalsAndDeparture(BaseModel): - actual_track: Optional[str] = FieldInfo(alias="actualTrack", default=None) - """The actual track information of the arriving transit vehicle.""" - - arrival_enabled: Optional[bool] = FieldInfo(alias="arrivalEnabled", default=None) + arrival_enabled: bool = FieldInfo(alias="arrivalEnabled") """Indicates if riders can arrive on this transit vehicle.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of this arrival’s trip into the sequence of trips for the active block.""" - departure_enabled: Optional[bool] = FieldInfo(alias="departureEnabled", default=None) + departure_enabled: bool = FieldInfo(alias="departureEnabled") """Indicates if riders can depart from this transit vehicle.""" + number_of_stops_away: int = FieldInfo(alias="numberOfStopsAway") + """ + Number of stops between the arriving transit vehicle and the current stop + (excluding the current stop). + """ + + predicted_arrival_time: int = FieldInfo(alias="predictedArrivalTime") + """ + Predicted arrival time, in milliseconds since Unix epoch (zero if no real-time + available). + """ + + predicted_departure_time: int = FieldInfo(alias="predictedDepartureTime") + """ + Predicted departure time, in milliseconds since Unix epoch (zero if no real-time + available). + """ + + route_id: str = FieldInfo(alias="routeId") + """The ID of the route for the arriving vehicle.""" + + scheduled_arrival_time: int = FieldInfo(alias="scheduledArrivalTime") + """Scheduled arrival time, in milliseconds since Unix epoch.""" + + scheduled_departure_time: int = FieldInfo(alias="scheduledDepartureTime") + """Scheduled departure time, in milliseconds since Unix epoch.""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + stop_id: str = FieldInfo(alias="stopId") + """The ID of the stop the vehicle is arriving at.""" + + stop_sequence: int = FieldInfo(alias="stopSequence") + """ + Index of the stop into the sequence of stops that make up the trip for this + arrival. + """ + + total_stops_in_trip: int = FieldInfo(alias="totalStopsInTrip") + """Total number of stops visited on the trip for this arrival.""" + + trip_headsign: str = FieldInfo(alias="tripHeadsign") + """ + Optional trip headsign that potentially overrides the trip headsign in the + referenced trip element. + """ + + trip_id: str = FieldInfo(alias="tripId") + """The ID of the trip for the arriving vehicle.""" + + vehicle_id: str = FieldInfo(alias="vehicleId") + """ID of the transit vehicle serving this trip.""" + + actual_track: Optional[str] = FieldInfo(alias="actualTrack", default=None) + """The actual track information of the arriving transit vehicle.""" + distance_from_stop: Optional[float] = FieldInfo(alias="distanceFromStop", default=None) """Distance of the arriving transit vehicle from the stop, in meters.""" @@ -160,12 +223,6 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDeparture(BaseModel): last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) """Timestamp of the last update time for this arrival.""" - number_of_stops_away: Optional[int] = FieldInfo(alias="numberOfStopsAway", default=None) - """ - Number of stops between the arriving transit vehicle and the current stop - (excluding the current stop). - """ - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) """Current occupancy status of the transit vehicle.""" @@ -175,27 +232,12 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDeparture(BaseModel): predicted_arrival_interval: Optional[str] = FieldInfo(alias="predictedArrivalInterval", default=None) """Interval for predicted arrival time, if available.""" - predicted_arrival_time: Optional[int] = FieldInfo(alias="predictedArrivalTime", default=None) - """ - Predicted arrival time, in milliseconds since Unix epoch (zero if no real-time - available). - """ - predicted_departure_interval: Optional[str] = FieldInfo(alias="predictedDepartureInterval", default=None) """Interval for predicted departure time, if available.""" - predicted_departure_time: Optional[int] = FieldInfo(alias="predictedDepartureTime", default=None) - """ - Predicted departure time, in milliseconds since Unix epoch (zero if no real-time - available). - """ - predicted_occupancy: Optional[str] = FieldInfo(alias="predictedOccupancy", default=None) """Predicted occupancy status of the transit vehicle.""" - route_id: Optional[str] = FieldInfo(alias="routeId", default=None) - """The ID of the route for the arriving vehicle.""" - route_long_name: Optional[str] = FieldInfo(alias="routeLongName", default=None) """ Optional route long name that potentially overrides the route long name in the @@ -211,59 +253,23 @@ class ArrivalAndDepartureListResponseDataEntryArrivalsAndDeparture(BaseModel): scheduled_arrival_interval: Optional[str] = FieldInfo(alias="scheduledArrivalInterval", default=None) """Interval for scheduled arrival time.""" - scheduled_arrival_time: Optional[int] = FieldInfo(alias="scheduledArrivalTime", default=None) - """Scheduled arrival time, in milliseconds since Unix epoch.""" - scheduled_departure_interval: Optional[str] = FieldInfo(alias="scheduledDepartureInterval", default=None) """Interval for scheduled departure time.""" - scheduled_departure_time: Optional[int] = FieldInfo(alias="scheduledDepartureTime", default=None) - """Scheduled departure time, in milliseconds since Unix epoch.""" - scheduled_track: Optional[str] = FieldInfo(alias="scheduledTrack", default=None) """Scheduled track information of the arriving transit vehicle.""" - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this arrival.""" status: Optional[str] = None """Current status of the arrival.""" - stop_id: Optional[str] = FieldInfo(alias="stopId", default=None) - """The ID of the stop the vehicle is arriving at.""" - - stop_sequence: Optional[int] = FieldInfo(alias="stopSequence", default=None) - """ - Index of the stop into the sequence of stops that make up the trip for this - arrival. - """ - - total_stops_in_trip: Optional[int] = FieldInfo(alias="totalStopsInTrip", default=None) - """Total number of stops visited on the trip for this arrival.""" - - trip_headsign: Optional[str] = FieldInfo(alias="tripHeadsign", default=None) - """ - Optional trip headsign that potentially overrides the trip headsign in the - referenced trip element. - """ - - trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) - """The ID of the trip for the arriving vehicle.""" - trip_status: Optional[ArrivalAndDepartureListResponseDataEntryArrivalsAndDepartureTripStatus] = FieldInfo( alias="tripStatus", default=None ) """Trip-specific status for the arriving transit vehicle.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) - """ID of the transit vehicle serving this trip.""" - class ArrivalAndDepartureListResponseDataEntry(BaseModel): arrivals_and_departures: List[ArrivalAndDepartureListResponseDataEntryArrivalsAndDeparture] = FieldInfo( @@ -278,4 +284,4 @@ class ArrivalAndDepartureListResponseData(BaseModel): class ArrivalAndDepartureListResponse(ResponseWrapper): - data: Optional[ArrivalAndDepartureListResponseData] = None + data: ArrivalAndDepartureListResponseData diff --git a/src/onebusaway/types/arrival_and_departure_retrieve_response.py b/src/onebusaway/types/arrival_and_departure_retrieve_response.py index 723eec72..b45d300e 100644 --- a/src/onebusaway/types/arrival_and_departure_retrieve_response.py +++ b/src/onebusaway/types/arrival_and_departure_retrieve_response.py @@ -19,6 +19,8 @@ class ArrivalAndDepartureRetrieveResponseDataEntryTripStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + lat: Optional[float] = None """Latitude of the last known location of the transit vehicle.""" @@ -27,6 +29,8 @@ class ArrivalAndDepartureRetrieveResponseDataEntryTripStatusLastKnownLocation(Ba class ArrivalAndDepartureRetrieveResponseDataEntryTripStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + lat: Optional[float] = None """Latitude of the current position of the transit vehicle.""" @@ -35,47 +39,79 @@ class ArrivalAndDepartureRetrieveResponseDataEntryTripStatusPosition(BaseModel): class ArrivalAndDepartureRetrieveResponseDataEntryTripStatus(BaseModel): - active_trip_id: Optional[str] = FieldInfo(alias="activeTripId", default=None) + """Trip-specific status for the arriving transit vehicle.""" + + active_trip_id: str = FieldInfo(alias="activeTripId") """Trip ID of the trip the vehicle is actively serving.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of the active trip into the sequence of trips for the active block.""" - closest_stop: Optional[str] = FieldInfo(alias="closestStop", default=None) + closest_stop: str = FieldInfo(alias="closestStop") """ID of the closest stop to the current location of the transit vehicle.""" + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) """ Time offset from the closest stop to the current position of the transit vehicle (in seconds). """ - distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) - """Distance, in meters, the transit vehicle has progressed along the active trip.""" - frequency: Optional[str] = None """Information about frequency-based scheduling, if applicable to the trip.""" - last_known_distance_along_trip: Optional[float] = FieldInfo(alias="lastKnownDistanceAlongTrip", default=None) - """ - Last known distance along the trip received in real-time from the transit - vehicle. - """ - last_known_location: Optional[ArrivalAndDepartureRetrieveResponseDataEntryTripStatusLastKnownLocation] = FieldInfo( alias="lastKnownLocation", default=None ) - """Last known location of the transit vehicle.""" + """Last known location of the transit vehicle (optional).""" last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) """Last known orientation value received in real-time from the transit vehicle.""" - last_location_update_time: Optional[int] = FieldInfo(alias="lastLocationUpdateTime", default=None) - """Timestamp of the last known real-time location update from the transit vehicle.""" - - last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) - """Timestamp of the last known real-time update from the transit vehicle.""" - next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) """ID of the next stop the transit vehicle is scheduled to arrive at.""" @@ -85,68 +121,95 @@ class ArrivalAndDepartureRetrieveResponseDataEntryTripStatus(BaseModel): (in seconds). """ - occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) - """Capacity of the transit vehicle in terms of occupancy.""" - - occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) - """Current count of occupants in the transit vehicle.""" - - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) - """Current occupancy status of the transit vehicle.""" - orientation: Optional[float] = None """Orientation of the transit vehicle, represented as an angle in degrees.""" - phase: Optional[str] = None - """Current journey phase of the trip.""" - position: Optional[ArrivalAndDepartureRetrieveResponseDataEntryTripStatusPosition] = None """Current position of the transit vehicle.""" - predicted: Optional[bool] = None - """Indicates if real-time arrival info is available for this trip.""" - scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) """ Distance, in meters, the transit vehicle is scheduled to have progressed along the active trip. """ - schedule_deviation: Optional[int] = FieldInfo(alias="scheduleDeviation", default=None) - """Deviation from the schedule in seconds (positive for late, negative for early).""" - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this trip.""" - status: Optional[str] = None - """Current status modifiers for the trip.""" - - total_distance_along_trip: Optional[float] = FieldInfo(alias="totalDistanceAlongTrip", default=None) - """Total length of the trip, in meters.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) """ID of the transit vehicle currently serving the trip.""" class ArrivalAndDepartureRetrieveResponseDataEntry(BaseModel): - actual_track: Optional[str] = FieldInfo(alias="actualTrack", default=None) - """The actual track information of the arriving transit vehicle.""" - - arrival_enabled: Optional[bool] = FieldInfo(alias="arrivalEnabled", default=None) + arrival_enabled: bool = FieldInfo(alias="arrivalEnabled") """Indicates if riders can arrive on this transit vehicle.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of this arrival’s trip into the sequence of trips for the active block.""" - departure_enabled: Optional[bool] = FieldInfo(alias="departureEnabled", default=None) + departure_enabled: bool = FieldInfo(alias="departureEnabled") """Indicates if riders can depart from this transit vehicle.""" + number_of_stops_away: int = FieldInfo(alias="numberOfStopsAway") + """ + Number of stops between the arriving transit vehicle and the current stop + (excluding the current stop). + """ + + predicted_arrival_time: int = FieldInfo(alias="predictedArrivalTime") + """ + Predicted arrival time, in milliseconds since Unix epoch (zero if no real-time + available). + """ + + predicted_departure_time: int = FieldInfo(alias="predictedDepartureTime") + """ + Predicted departure time, in milliseconds since Unix epoch (zero if no real-time + available). + """ + + route_id: str = FieldInfo(alias="routeId") + """The ID of the route for the arriving vehicle.""" + + scheduled_arrival_time: int = FieldInfo(alias="scheduledArrivalTime") + """Scheduled arrival time, in milliseconds since Unix epoch.""" + + scheduled_departure_time: int = FieldInfo(alias="scheduledDepartureTime") + """Scheduled departure time, in milliseconds since Unix epoch.""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + stop_id: str = FieldInfo(alias="stopId") + """The ID of the stop the vehicle is arriving at.""" + + stop_sequence: int = FieldInfo(alias="stopSequence") + """ + Index of the stop into the sequence of stops that make up the trip for this + arrival. + """ + + total_stops_in_trip: int = FieldInfo(alias="totalStopsInTrip") + """Total number of stops visited on the trip for this arrival.""" + + trip_headsign: str = FieldInfo(alias="tripHeadsign") + """ + Optional trip headsign that potentially overrides the trip headsign in the + referenced trip element. + """ + + trip_id: str = FieldInfo(alias="tripId") + """The ID of the trip for the arriving vehicle.""" + + vehicle_id: str = FieldInfo(alias="vehicleId") + """ID of the transit vehicle serving this trip.""" + + actual_track: Optional[str] = FieldInfo(alias="actualTrack", default=None) + """The actual track information of the arriving transit vehicle.""" + distance_from_stop: Optional[float] = FieldInfo(alias="distanceFromStop", default=None) """Distance of the arriving transit vehicle from the stop, in meters.""" @@ -159,12 +222,6 @@ class ArrivalAndDepartureRetrieveResponseDataEntry(BaseModel): last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) """Timestamp of the last update time for this arrival.""" - number_of_stops_away: Optional[int] = FieldInfo(alias="numberOfStopsAway", default=None) - """ - Number of stops between the arriving transit vehicle and the current stop - (excluding the current stop). - """ - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) """Current occupancy status of the transit vehicle.""" @@ -174,27 +231,12 @@ class ArrivalAndDepartureRetrieveResponseDataEntry(BaseModel): predicted_arrival_interval: Optional[str] = FieldInfo(alias="predictedArrivalInterval", default=None) """Interval for predicted arrival time, if available.""" - predicted_arrival_time: Optional[int] = FieldInfo(alias="predictedArrivalTime", default=None) - """ - Predicted arrival time, in milliseconds since Unix epoch (zero if no real-time - available). - """ - predicted_departure_interval: Optional[str] = FieldInfo(alias="predictedDepartureInterval", default=None) """Interval for predicted departure time, if available.""" - predicted_departure_time: Optional[int] = FieldInfo(alias="predictedDepartureTime", default=None) - """ - Predicted departure time, in milliseconds since Unix epoch (zero if no real-time - available). - """ - predicted_occupancy: Optional[str] = FieldInfo(alias="predictedOccupancy", default=None) """Predicted occupancy status of the transit vehicle.""" - route_id: Optional[str] = FieldInfo(alias="routeId", default=None) - """The ID of the route for the arriving vehicle.""" - route_long_name: Optional[str] = FieldInfo(alias="routeLongName", default=None) """ Optional route long name that potentially overrides the route long name in the @@ -210,59 +252,23 @@ class ArrivalAndDepartureRetrieveResponseDataEntry(BaseModel): scheduled_arrival_interval: Optional[str] = FieldInfo(alias="scheduledArrivalInterval", default=None) """Interval for scheduled arrival time.""" - scheduled_arrival_time: Optional[int] = FieldInfo(alias="scheduledArrivalTime", default=None) - """Scheduled arrival time, in milliseconds since Unix epoch.""" - scheduled_departure_interval: Optional[str] = FieldInfo(alias="scheduledDepartureInterval", default=None) """Interval for scheduled departure time.""" - scheduled_departure_time: Optional[int] = FieldInfo(alias="scheduledDepartureTime", default=None) - """Scheduled departure time, in milliseconds since Unix epoch.""" - scheduled_track: Optional[str] = FieldInfo(alias="scheduledTrack", default=None) """Scheduled track information of the arriving transit vehicle.""" - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this arrival.""" status: Optional[str] = None """Current status of the arrival.""" - stop_id: Optional[str] = FieldInfo(alias="stopId", default=None) - """The ID of the stop the vehicle is arriving at.""" - - stop_sequence: Optional[int] = FieldInfo(alias="stopSequence", default=None) - """ - Index of the stop into the sequence of stops that make up the trip for this - arrival. - """ - - total_stops_in_trip: Optional[int] = FieldInfo(alias="totalStopsInTrip", default=None) - """Total number of stops visited on the trip for this arrival.""" - - trip_headsign: Optional[str] = FieldInfo(alias="tripHeadsign", default=None) - """ - Optional trip headsign that potentially overrides the trip headsign in the - referenced trip element. - """ - - trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) - """The ID of the trip for the arriving vehicle.""" - trip_status: Optional[ArrivalAndDepartureRetrieveResponseDataEntryTripStatus] = FieldInfo( alias="tripStatus", default=None ) """Trip-specific status for the arriving transit vehicle.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) - """ID of the transit vehicle serving this trip.""" - class ArrivalAndDepartureRetrieveResponseData(BaseModel): entry: ArrivalAndDepartureRetrieveResponseDataEntry @@ -271,4 +277,4 @@ class ArrivalAndDepartureRetrieveResponseData(BaseModel): class ArrivalAndDepartureRetrieveResponse(ResponseWrapper): - data: Optional[ArrivalAndDepartureRetrieveResponseData] = None + data: ArrivalAndDepartureRetrieveResponseData diff --git a/src/onebusaway/types/block_retrieve_response.py b/src/onebusaway/types/block_retrieve_response.py new file mode 100644 index 00000000..aecb822f --- /dev/null +++ b/src/onebusaway/types/block_retrieve_response.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "BlockRetrieveResponse", + "BlockRetrieveResponseData", + "BlockRetrieveResponseDataEntry", + "BlockRetrieveResponseDataEntryConfiguration", + "BlockRetrieveResponseDataEntryConfigurationTrip", + "BlockRetrieveResponseDataEntryConfigurationTripBlockStopTime", + "BlockRetrieveResponseDataEntryConfigurationTripBlockStopTimeStopTime", +] + + +class BlockRetrieveResponseDataEntryConfigurationTripBlockStopTimeStopTime(BaseModel): + arrival_time: int = FieldInfo(alias="arrivalTime") + + departure_time: int = FieldInfo(alias="departureTime") + + stop_id: str = FieldInfo(alias="stopId") + + drop_off_type: Optional[int] = FieldInfo(alias="dropOffType", default=None) + + pickup_type: Optional[int] = FieldInfo(alias="pickupType", default=None) + + +class BlockRetrieveResponseDataEntryConfigurationTripBlockStopTime(BaseModel): + accumulated_slack_time: float = FieldInfo(alias="accumulatedSlackTime") + + block_sequence: int = FieldInfo(alias="blockSequence") + + distance_along_block: float = FieldInfo(alias="distanceAlongBlock") + + stop_time: BlockRetrieveResponseDataEntryConfigurationTripBlockStopTimeStopTime = FieldInfo(alias="stopTime") + + +class BlockRetrieveResponseDataEntryConfigurationTrip(BaseModel): + accumulated_slack_time: float = FieldInfo(alias="accumulatedSlackTime") + + block_stop_times: List[BlockRetrieveResponseDataEntryConfigurationTripBlockStopTime] = FieldInfo( + alias="blockStopTimes" + ) + + distance_along_block: float = FieldInfo(alias="distanceAlongBlock") + + trip_id: str = FieldInfo(alias="tripId") + + +class BlockRetrieveResponseDataEntryConfiguration(BaseModel): + active_service_ids: List[str] = FieldInfo(alias="activeServiceIds") + + trips: List[BlockRetrieveResponseDataEntryConfigurationTrip] + + inactive_service_ids: Optional[List[str]] = FieldInfo(alias="inactiveServiceIds", default=None) + + +class BlockRetrieveResponseDataEntry(BaseModel): + id: str + + configurations: List[BlockRetrieveResponseDataEntryConfiguration] + + +class BlockRetrieveResponseData(BaseModel): + entry: BlockRetrieveResponseDataEntry + + references: References + + +class BlockRetrieveResponse(ResponseWrapper): + data: BlockRetrieveResponseData diff --git a/src/onebusaway/types/config_retrieve_response.py b/src/onebusaway/types/config_retrieve_response.py index fb24ae4b..eaa7cf91 100644 --- a/src/onebusaway/types/config_retrieve_response.py +++ b/src/onebusaway/types/config_retrieve_response.py @@ -79,4 +79,4 @@ class ConfigRetrieveResponseData(BaseModel): class ConfigRetrieveResponse(ResponseWrapper): - data: Optional[ConfigRetrieveResponseData] = None + data: ConfigRetrieveResponseData diff --git a/src/onebusaway/types/current_time_retrieve_response.py b/src/onebusaway/types/current_time_retrieve_response.py index 0f0ad3b9..20fddb2d 100644 --- a/src/onebusaway/types/current_time_retrieve_response.py +++ b/src/onebusaway/types/current_time_retrieve_response.py @@ -24,4 +24,4 @@ class CurrentTimeRetrieveResponseData(BaseModel): class CurrentTimeRetrieveResponse(ResponseWrapper): - data: Optional[CurrentTimeRetrieveResponseData] = None + data: CurrentTimeRetrieveResponseData diff --git a/src/onebusaway/types/report_problem_with_stop_retrieve_params.py b/src/onebusaway/types/report_problem_with_stop_retrieve_params.py new file mode 100644 index 00000000..609f03ff --- /dev/null +++ b/src/onebusaway/types/report_problem_with_stop_retrieve_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ReportProblemWithStopRetrieveParams"] + + +class ReportProblemWithStopRetrieveParams(TypedDict, total=False): + code: Literal["stop_name_wrong", "stop_number_wrong", "stop_location_wrong", "route_or_trip_missing", "other"] + """A string code identifying the nature of the problem""" + + user_comment: Annotated[str, PropertyInfo(alias="userComment")] + """Additional comment text supplied by the user describing the problem""" + + user_lat: Annotated[float, PropertyInfo(alias="userLat")] + """The reporting user’s current latitude""" + + user_location_accuracy: Annotated[float, PropertyInfo(alias="userLocationAccuracy")] + """The reporting user’s location accuracy, in meters""" + + user_lon: Annotated[float, PropertyInfo(alias="userLon")] + """The reporting user’s current longitude""" diff --git a/src/onebusaway/types/report_problem_with_trip_retrieve_params.py b/src/onebusaway/types/report_problem_with_trip_retrieve_params.py new file mode 100644 index 00000000..f55386d4 --- /dev/null +++ b/src/onebusaway/types/report_problem_with_trip_retrieve_params.py @@ -0,0 +1,48 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ReportProblemWithTripRetrieveParams"] + + +class ReportProblemWithTripRetrieveParams(TypedDict, total=False): + code: Literal[ + "vehicle_never_came", + "vehicle_came_early", + "vehicle_came_late", + "wrong_headsign", + "vehicle_does_not_stop_here", + "other", + ] + """A string code identifying the nature of the problem""" + + service_date: Annotated[int, PropertyInfo(alias="serviceDate")] + """The service date of the trip""" + + stop_id: Annotated[str, PropertyInfo(alias="stopID")] + """A stop ID indicating where the user is experiencing the problem""" + + user_comment: Annotated[str, PropertyInfo(alias="userComment")] + """Additional comment text supplied by the user describing the problem""" + + user_lat: Annotated[float, PropertyInfo(alias="userLat")] + """The reporting user’s current latitude""" + + user_location_accuracy: Annotated[float, PropertyInfo(alias="userLocationAccuracy")] + """The reporting user’s location accuracy, in meters""" + + user_lon: Annotated[float, PropertyInfo(alias="userLon")] + """The reporting user’s current longitude""" + + user_on_vehicle: Annotated[bool, PropertyInfo(alias="userOnVehicle")] + """Indicator if the user is on the transit vehicle experiencing the problem""" + + user_vehicle_number: Annotated[str, PropertyInfo(alias="userVehicleNumber")] + """The vehicle number, as reported by the user""" + + vehicle_id: Annotated[str, PropertyInfo(alias="vehicleID")] + """The vehicle actively serving the trip""" diff --git a/src/onebusaway/types/route_ids_for_agency_list_response.py b/src/onebusaway/types/route_ids_for_agency_list_response.py new file mode 100644 index 00000000..ffe9c324 --- /dev/null +++ b/src/onebusaway/types/route_ids_for_agency_list_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["RouteIDsForAgencyListResponse", "RouteIDsForAgencyListResponseData"] + + +class RouteIDsForAgencyListResponseData(BaseModel): + list: List[str] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class RouteIDsForAgencyListResponse(ResponseWrapper): + data: RouteIDsForAgencyListResponseData diff --git a/src/onebusaway/types/route_retrieve_response.py b/src/onebusaway/types/route_retrieve_response.py index cf07d4ac..a0dfa1ef 100644 --- a/src/onebusaway/types/route_retrieve_response.py +++ b/src/onebusaway/types/route_retrieve_response.py @@ -12,9 +12,11 @@ class RouteRetrieveResponseDataEntry(BaseModel): - id: Optional[str] = None + id: str - agency_id: Optional[str] = FieldInfo(alias="agencyId", default=None) + agency_id: str = FieldInfo(alias="agencyId") + + type: int color: Optional[str] = None @@ -22,12 +24,12 @@ class RouteRetrieveResponseDataEntry(BaseModel): long_name: Optional[str] = FieldInfo(alias="longName", default=None) + null_safe_short_name: Optional[str] = FieldInfo(alias="nullSafeShortName", default=None) + short_name: Optional[str] = FieldInfo(alias="shortName", default=None) text_color: Optional[str] = FieldInfo(alias="textColor", default=None) - type: Optional[int] = None - url: Optional[str] = None @@ -38,4 +40,4 @@ class RouteRetrieveResponseData(BaseModel): class RouteRetrieveResponse(ResponseWrapper): - data: Optional[RouteRetrieveResponseData] = None + data: RouteRetrieveResponseData diff --git a/src/onebusaway/types/routes_for_agency_list_response.py b/src/onebusaway/types/routes_for_agency_list_response.py new file mode 100644 index 00000000..34ddc665 --- /dev/null +++ b/src/onebusaway/types/routes_for_agency_list_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["RoutesForAgencyListResponse", "RoutesForAgencyListResponseData", "RoutesForAgencyListResponseDataList"] + + +class RoutesForAgencyListResponseDataList(BaseModel): + id: str + + agency_id: str = FieldInfo(alias="agencyId") + + type: int + + color: Optional[str] = None + + description: Optional[str] = None + + long_name: Optional[str] = FieldInfo(alias="longName", default=None) + + null_safe_short_name: Optional[str] = FieldInfo(alias="nullSafeShortName", default=None) + + short_name: Optional[str] = FieldInfo(alias="shortName", default=None) + + text_color: Optional[str] = FieldInfo(alias="textColor", default=None) + + url: Optional[str] = None + + +class RoutesForAgencyListResponseData(BaseModel): + list: List[RoutesForAgencyListResponseDataList] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class RoutesForAgencyListResponse(ResponseWrapper): + data: RoutesForAgencyListResponseData diff --git a/src/onebusaway/types/routes_for_location_list_params.py b/src/onebusaway/types/routes_for_location_list_params.py new file mode 100644 index 00000000..033ec2a6 --- /dev/null +++ b/src/onebusaway/types/routes_for_location_list_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["RoutesForLocationListParams"] + + +class RoutesForLocationListParams(TypedDict, total=False): + lat: Required[float] + + lon: Required[float] + + lat_span: Annotated[float, PropertyInfo(alias="latSpan")] + + lon_span: Annotated[float, PropertyInfo(alias="lonSpan")] + + query: str + + radius: float diff --git a/src/onebusaway/types/routes_for_location_list_response.py b/src/onebusaway/types/routes_for_location_list_response.py new file mode 100644 index 00000000..aebd12d3 --- /dev/null +++ b/src/onebusaway/types/routes_for_location_list_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "RoutesForLocationListResponse", + "RoutesForLocationListResponseData", + "RoutesForLocationListResponseDataList", +] + + +class RoutesForLocationListResponseDataList(BaseModel): + id: str + + agency_id: str = FieldInfo(alias="agencyId") + + type: int + + color: Optional[str] = None + + description: Optional[str] = None + + long_name: Optional[str] = FieldInfo(alias="longName", default=None) + + null_safe_short_name: Optional[str] = FieldInfo(alias="nullSafeShortName", default=None) + + short_name: Optional[str] = FieldInfo(alias="shortName", default=None) + + text_color: Optional[str] = FieldInfo(alias="textColor", default=None) + + url: Optional[str] = None + + +class RoutesForLocationListResponseData(BaseModel): + list: List[RoutesForLocationListResponseDataList] + + out_of_range: bool = FieldInfo(alias="outOfRange") + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class RoutesForLocationListResponse(ResponseWrapper): + data: RoutesForLocationListResponseData diff --git a/src/onebusaway/types/schedule_for_route_retrieve_params.py b/src/onebusaway/types/schedule_for_route_retrieve_params.py new file mode 100644 index 00000000..6d4d0299 --- /dev/null +++ b/src/onebusaway/types/schedule_for_route_retrieve_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import datetime +from typing import Union +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ScheduleForRouteRetrieveParams"] + + +class ScheduleForRouteRetrieveParams(TypedDict, total=False): + date: Annotated[Union[str, datetime.date], PropertyInfo(format="iso8601")] + """ + The date for which you want to request a schedule in the format YYYY-MM-DD + (optional, defaults to current date) + """ diff --git a/src/onebusaway/types/schedule_for_route_retrieve_response.py b/src/onebusaway/types/schedule_for_route_retrieve_response.py new file mode 100644 index 00000000..e749333d --- /dev/null +++ b/src/onebusaway/types/schedule_for_route_retrieve_response.py @@ -0,0 +1,77 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "ScheduleForRouteRetrieveResponse", + "ScheduleForRouteRetrieveResponseData", + "ScheduleForRouteRetrieveResponseDataEntry", + "ScheduleForRouteRetrieveResponseDataEntryStopTripGrouping", + "ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTime", + "ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTimeStopTime", +] + + +class ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTimeStopTime(BaseModel): + arrival_enabled: bool = FieldInfo(alias="arrivalEnabled") + + arrival_time: int = FieldInfo(alias="arrivalTime") + + departure_enabled: bool = FieldInfo(alias="departureEnabled") + + departure_time: int = FieldInfo(alias="departureTime") + + stop_id: str = FieldInfo(alias="stopId") + + trip_id: str = FieldInfo(alias="tripId") + + service_id: Optional[str] = FieldInfo(alias="serviceId", default=None) + + stop_headsign: Optional[str] = FieldInfo(alias="stopHeadsign", default=None) + + +class ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTime(BaseModel): + stop_times: List[ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTimeStopTime] = FieldInfo( + alias="stopTimes" + ) + + trip_id: str = FieldInfo(alias="tripId") + + +class ScheduleForRouteRetrieveResponseDataEntryStopTripGrouping(BaseModel): + direction_id: str = FieldInfo(alias="directionId") + + stop_ids: List[str] = FieldInfo(alias="stopIds") + + trip_headsigns: List[str] = FieldInfo(alias="tripHeadsigns") + + trip_ids: List[str] = FieldInfo(alias="tripIds") + + trips_with_stop_times: Optional[ + List[ScheduleForRouteRetrieveResponseDataEntryStopTripGroupingTripsWithStopTime] + ] = FieldInfo(alias="tripsWithStopTimes", default=None) + + +class ScheduleForRouteRetrieveResponseDataEntry(BaseModel): + route_id: str = FieldInfo(alias="routeId") + + schedule_date: int = FieldInfo(alias="scheduleDate") + + service_ids: List[str] = FieldInfo(alias="serviceIds") + + stop_trip_groupings: List[ScheduleForRouteRetrieveResponseDataEntryStopTripGrouping] = FieldInfo( + alias="stopTripGroupings" + ) + + +class ScheduleForRouteRetrieveResponseData(BaseModel): + entry: ScheduleForRouteRetrieveResponseDataEntry + + +class ScheduleForRouteRetrieveResponse(ResponseWrapper): + data: ScheduleForRouteRetrieveResponseData diff --git a/src/onebusaway/types/schedule_for_stop_retrieve_response.py b/src/onebusaway/types/schedule_for_stop_retrieve_response.py index 5170464a..83d19854 100644 --- a/src/onebusaway/types/schedule_for_stop_retrieve_response.py +++ b/src/onebusaway/types/schedule_for_stop_retrieve_response.py @@ -86,4 +86,4 @@ class ScheduleForStopRetrieveResponseData(BaseModel): class ScheduleForStopRetrieveResponse(ResponseWrapper): - data: Optional[ScheduleForStopRetrieveResponseData] = None + data: ScheduleForStopRetrieveResponseData diff --git a/src/onebusaway/types/search_for_route_list_params.py b/src/onebusaway/types/search_for_route_list_params.py new file mode 100755 index 00000000..76f767b6 --- /dev/null +++ b/src/onebusaway/types/search_for_route_list_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["SearchForRouteListParams"] + + +class SearchForRouteListParams(TypedDict, total=False): + input: Required[str] + """The string to search for.""" + + max_count: Annotated[int, PropertyInfo(alias="maxCount")] + """The max number of results to return. Defaults to 20.""" diff --git a/src/onebusaway/types/search_for_route_list_response.py b/src/onebusaway/types/search_for_route_list_response.py new file mode 100755 index 00000000..f6b53f7a --- /dev/null +++ b/src/onebusaway/types/search_for_route_list_response.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["SearchForRouteListResponse", "SearchForRouteListResponseData", "SearchForRouteListResponseDataList"] + + +class SearchForRouteListResponseDataList(BaseModel): + id: str + + agency_id: str = FieldInfo(alias="agencyId") + + type: int + + color: Optional[str] = None + + description: Optional[str] = None + + long_name: Optional[str] = FieldInfo(alias="longName", default=None) + + null_safe_short_name: Optional[str] = FieldInfo(alias="nullSafeShortName", default=None) + + short_name: Optional[str] = FieldInfo(alias="shortName", default=None) + + text_color: Optional[str] = FieldInfo(alias="textColor", default=None) + + url: Optional[str] = None + + +class SearchForRouteListResponseData(BaseModel): + list: List[SearchForRouteListResponseDataList] + + out_of_range: bool = FieldInfo(alias="outOfRange") + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class SearchForRouteListResponse(ResponseWrapper): + data: Optional[SearchForRouteListResponseData] = None diff --git a/src/onebusaway/types/search_for_route_retrieve_params.py b/src/onebusaway/types/search_for_route_retrieve_params.py new file mode 100755 index 00000000..08e80862 --- /dev/null +++ b/src/onebusaway/types/search_for_route_retrieve_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["SearchForRouteRetrieveParams"] + + +class SearchForRouteRetrieveParams(TypedDict, total=False): + input: Required[str] + """The string to search for.""" + + max_count: Annotated[int, PropertyInfo(alias="maxCount")] + """The max number of results to return. Defaults to 20.""" diff --git a/src/onebusaway/types/search_for_route_retrieve_response.py b/src/onebusaway/types/search_for_route_retrieve_response.py new file mode 100755 index 00000000..0ed8dc39 --- /dev/null +++ b/src/onebusaway/types/search_for_route_retrieve_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "SearchForRouteRetrieveResponse", + "SearchForRouteRetrieveResponseData", + "SearchForRouteRetrieveResponseDataList", +] + + +class SearchForRouteRetrieveResponseDataList(BaseModel): + id: str + + agency_id: str = FieldInfo(alias="agencyId") + + type: int + + color: Optional[str] = None + + description: Optional[str] = None + + long_name: Optional[str] = FieldInfo(alias="longName", default=None) + + null_safe_short_name: Optional[str] = FieldInfo(alias="nullSafeShortName", default=None) + + short_name: Optional[str] = FieldInfo(alias="shortName", default=None) + + text_color: Optional[str] = FieldInfo(alias="textColor", default=None) + + url: Optional[str] = None + + +class SearchForRouteRetrieveResponseData(BaseModel): + limit_exceeded: bool = FieldInfo(alias="limitExceeded") + + list: List[SearchForRouteRetrieveResponseDataList] + + out_of_range: bool = FieldInfo(alias="outOfRange") + + references: References + + +class SearchForRouteRetrieveResponse(ResponseWrapper): + data: Optional[SearchForRouteRetrieveResponseData] = None diff --git a/src/onebusaway/types/search_for_stop_list_params.py b/src/onebusaway/types/search_for_stop_list_params.py new file mode 100755 index 00000000..012f8025 --- /dev/null +++ b/src/onebusaway/types/search_for_stop_list_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["SearchForStopListParams"] + + +class SearchForStopListParams(TypedDict, total=False): + input: Required[str] + """The string to search for.""" + + max_count: Annotated[int, PropertyInfo(alias="maxCount")] + """The max number of results to return. Defaults to 20.""" diff --git a/src/onebusaway/types/search_for_stop_list_response.py b/src/onebusaway/types/search_for_stop_list_response.py new file mode 100755 index 00000000..5c400839 --- /dev/null +++ b/src/onebusaway/types/search_for_stop_list_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["SearchForStopListResponse", "SearchForStopListResponseData", "SearchForStopListResponseDataList"] + + +class SearchForStopListResponseDataList(BaseModel): + id: str + + lat: float + + location_type: int = FieldInfo(alias="locationType") + + lon: float + + name: str + + parent: str + + route_ids: List[str] = FieldInfo(alias="routeIds") + + static_route_ids: List[str] = FieldInfo(alias="staticRouteIds") + + code: Optional[str] = None + + direction: Optional[str] = None + + wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) + + +class SearchForStopListResponseData(BaseModel): + list: List[SearchForStopListResponseDataList] + + out_of_range: bool = FieldInfo(alias="outOfRange") + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class SearchForStopListResponse(ResponseWrapper): + data: Optional[SearchForStopListResponseData] = None diff --git a/src/onebusaway/types/search_for_stop_retrieve_params.py b/src/onebusaway/types/search_for_stop_retrieve_params.py new file mode 100755 index 00000000..2f478945 --- /dev/null +++ b/src/onebusaway/types/search_for_stop_retrieve_params.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["SearchForStopRetrieveParams"] + + +class SearchForStopRetrieveParams(TypedDict, total=False): + input: Required[str] + """The string to search for.""" + + max_count: Annotated[int, PropertyInfo(alias="maxCount")] + """The max number of results to return. Defaults to 20.""" diff --git a/src/onebusaway/types/stops_for_location_retrieve_response.py b/src/onebusaway/types/search_for_stop_retrieve_response.py old mode 100644 new mode 100755 similarity index 53% rename from src/onebusaway/types/stops_for_location_retrieve_response.py rename to src/onebusaway/types/search_for_stop_retrieve_response.py index 36121682..5ac10ca8 --- a/src/onebusaway/types/stops_for_location_retrieve_response.py +++ b/src/onebusaway/types/search_for_stop_retrieve_response.py @@ -9,26 +9,26 @@ from .shared.response_wrapper import ResponseWrapper __all__ = [ - "StopsForLocationRetrieveResponse", - "StopsForLocationRetrieveResponseData", - "StopsForLocationRetrieveResponseDataList", + "SearchForStopRetrieveResponse", + "SearchForStopRetrieveResponseData", + "SearchForStopRetrieveResponseDataList", ] -class StopsForLocationRetrieveResponseDataList(BaseModel): - id: Optional[str] = None +class SearchForStopRetrieveResponseDataList(BaseModel): + id: str - code: Optional[str] = None + code: str - direction: Optional[str] = None + lat: float - lat: Optional[float] = None + lon: float - location_type: Optional[int] = FieldInfo(alias="locationType", default=None) + name: str - lon: Optional[float] = None + direction: Optional[str] = None - name: Optional[str] = None + location_type: Optional[int] = FieldInfo(alias="locationType", default=None) parent: Optional[str] = None @@ -39,13 +39,15 @@ class StopsForLocationRetrieveResponseDataList(BaseModel): wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) -class StopsForLocationRetrieveResponseData(BaseModel): - list: List[StopsForLocationRetrieveResponseDataList] +class SearchForStopRetrieveResponseData(BaseModel): + limit_exceeded: bool = FieldInfo(alias="limitExceeded") - references: References + list: List[SearchForStopRetrieveResponseDataList] + + out_of_range: bool = FieldInfo(alias="outOfRange") - limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + references: References -class StopsForLocationRetrieveResponse(ResponseWrapper): - data: Optional[StopsForLocationRetrieveResponseData] = None +class SearchForStopRetrieveResponse(ResponseWrapper): + data: Optional[SearchForStopRetrieveResponseData] = None diff --git a/src/onebusaway/types/shape_retrieve_response.py b/src/onebusaway/types/shape_retrieve_response.py new file mode 100644 index 00000000..07a3b6ac --- /dev/null +++ b/src/onebusaway/types/shape_retrieve_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["ShapeRetrieveResponse", "ShapeRetrieveResponseData", "ShapeRetrieveResponseDataEntry"] + + +class ShapeRetrieveResponseDataEntry(BaseModel): + length: int + + points: str + """Encoded polyline format representing the shape of the path""" + + levels: Optional[str] = None + + +class ShapeRetrieveResponseData(BaseModel): + entry: ShapeRetrieveResponseDataEntry + + references: References + + +class ShapeRetrieveResponse(ResponseWrapper): + data: ShapeRetrieveResponseData diff --git a/src/onebusaway/types/shared/references.py b/src/onebusaway/types/shared/references.py index 827d72aa..4a95520f 100644 --- a/src/onebusaway/types/shared/references.py +++ b/src/onebusaway/types/shared/references.py @@ -1,7 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional -from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -50,9 +49,11 @@ class Agency(BaseModel): class Route(BaseModel): - id: Optional[str] = None + id: str - agency_id: Optional[str] = FieldInfo(alias="agencyId", default=None) + agency_id: str = FieldInfo(alias="agencyId") + + type: int color: Optional[str] = None @@ -66,8 +67,6 @@ class Route(BaseModel): text_color: Optional[str] = FieldInfo(alias="textColor", default=None) - type: Optional[int] = None - url: Optional[str] = None @@ -181,9 +180,7 @@ class Situation(BaseModel): alias="publicationWindows", default=None ) - reason: Optional[ - Literal["equipmentReason", "environmentReason", "personnelReason", "miscellaneousReason", "securityAlert"] - ] = None + reason: Optional[str] = None """Reason for the service alert, taken from TPEG codes.""" severity: Optional[str] = None @@ -197,23 +194,23 @@ class Situation(BaseModel): class Stop(BaseModel): id: str - code: str - lat: float + location_type: int = FieldInfo(alias="locationType") + lon: float name: str - direction: Optional[str] = None + parent: str - location_type: Optional[int] = FieldInfo(alias="locationType", default=None) + route_ids: List[str] = FieldInfo(alias="routeIds") - parent: Optional[str] = None + static_route_ids: List[str] = FieldInfo(alias="staticRouteIds") - route_ids: Optional[List[str]] = FieldInfo(alias="routeIds", default=None) + code: Optional[str] = None - static_route_ids: Optional[List[str]] = FieldInfo(alias="staticRouteIds", default=None) + direction: Optional[str] = None wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) @@ -237,6 +234,8 @@ class Trip(BaseModel): route_id: str = FieldInfo(alias="routeId") + service_id: str = FieldInfo(alias="serviceId") + block_id: Optional[str] = FieldInfo(alias="blockId", default=None) direction_id: Optional[str] = FieldInfo(alias="directionId", default=None) @@ -245,8 +244,6 @@ class Trip(BaseModel): route_short_name: Optional[str] = FieldInfo(alias="routeShortName", default=None) - service_id: Optional[str] = FieldInfo(alias="serviceId", default=None) - shape_id: Optional[str] = FieldInfo(alias="shapeId", default=None) time_zone: Optional[str] = FieldInfo(alias="timeZone", default=None) @@ -257,14 +254,14 @@ class Trip(BaseModel): class References(BaseModel): - agencies: Optional[List[Agency]] = None + agencies: List[Agency] - routes: Optional[List[Route]] = None + routes: List[Route] - situations: Optional[List[Situation]] = None + situations: List[Situation] - stops: Optional[List[Stop]] = None + stops: List[Stop] - stop_times: Optional[List[StopTime]] = FieldInfo(alias="stopTimes", default=None) + stop_times: List[StopTime] = FieldInfo(alias="stopTimes") - trips: Optional[List[Trip]] = None + trips: List[Trip] diff --git a/src/onebusaway/types/shared/response_wrapper.py b/src/onebusaway/types/shared/response_wrapper.py index 72ecc38c..2adf5430 100644 --- a/src/onebusaway/types/shared/response_wrapper.py +++ b/src/onebusaway/types/shared/response_wrapper.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from pydantic import Field as FieldInfo from ..._models import BaseModel diff --git a/src/onebusaway/types/stop_ids_for_agency_list_response.py b/src/onebusaway/types/stop_ids_for_agency_list_response.py index bf3927cc..f36b63fc 100644 --- a/src/onebusaway/types/stop_ids_for_agency_list_response.py +++ b/src/onebusaway/types/stop_ids_for_agency_list_response.py @@ -20,4 +20,4 @@ class StopIDsForAgencyListResponseData(BaseModel): class StopIDsForAgencyListResponse(ResponseWrapper): - data: Optional[StopIDsForAgencyListResponseData] = None + data: StopIDsForAgencyListResponseData diff --git a/src/onebusaway/types/stop_retrieve_response.py b/src/onebusaway/types/stop_retrieve_response.py index 8d711834..0596ce7b 100644 --- a/src/onebusaway/types/stop_retrieve_response.py +++ b/src/onebusaway/types/stop_retrieve_response.py @@ -14,23 +14,23 @@ class StopRetrieveResponseDataEntry(BaseModel): id: str - code: str - lat: float + location_type: int = FieldInfo(alias="locationType") + lon: float name: str - direction: Optional[str] = None + parent: str - location_type: Optional[int] = FieldInfo(alias="locationType", default=None) + route_ids: List[str] = FieldInfo(alias="routeIds") - parent: Optional[str] = None + static_route_ids: List[str] = FieldInfo(alias="staticRouteIds") - route_ids: Optional[List[str]] = FieldInfo(alias="routeIds", default=None) + code: Optional[str] = None - static_route_ids: Optional[List[str]] = FieldInfo(alias="staticRouteIds", default=None) + direction: Optional[str] = None wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) @@ -42,4 +42,4 @@ class StopRetrieveResponseData(BaseModel): class StopRetrieveResponse(ResponseWrapper): - data: Optional[StopRetrieveResponseData] = None + data: StopRetrieveResponseData diff --git a/src/onebusaway/types/stops_for_agency_list_response.py b/src/onebusaway/types/stops_for_agency_list_response.py new file mode 100644 index 00000000..c44e7e3e --- /dev/null +++ b/src/onebusaway/types/stops_for_agency_list_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["StopsForAgencyListResponse", "StopsForAgencyListResponseData", "StopsForAgencyListResponseDataList"] + + +class StopsForAgencyListResponseDataList(BaseModel): + id: str + + lat: float + + location_type: int = FieldInfo(alias="locationType") + + lon: float + + name: str + + parent: str + + route_ids: List[str] = FieldInfo(alias="routeIds") + + static_route_ids: List[str] = FieldInfo(alias="staticRouteIds") + + code: Optional[str] = None + + direction: Optional[str] = None + + wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) + + +class StopsForAgencyListResponseData(BaseModel): + list: List[StopsForAgencyListResponseDataList] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + out_of_range: Optional[bool] = FieldInfo(alias="outOfRange", default=None) + + +class StopsForAgencyListResponse(ResponseWrapper): + data: StopsForAgencyListResponseData diff --git a/src/onebusaway/types/stops_for_location_list_params.py b/src/onebusaway/types/stops_for_location_list_params.py new file mode 100644 index 00000000..31742572 --- /dev/null +++ b/src/onebusaway/types/stops_for_location_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 Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["StopsForLocationListParams"] + + +class StopsForLocationListParams(TypedDict, total=False): + lat: Required[float] + + lon: Required[float] + + lat_span: Annotated[float, PropertyInfo(alias="latSpan")] + """An alternative to radius to set the search bounding box (optional)""" + + lon_span: Annotated[float, PropertyInfo(alias="lonSpan")] + """An alternative to radius to set the search bounding box (optional)""" + + query: str + """A search query string to filter the results""" + + radius: float + """The radius in meters to search within""" diff --git a/src/onebusaway/types/stops_for_location_list_response.py b/src/onebusaway/types/stops_for_location_list_response.py new file mode 100644 index 00000000..69234bbc --- /dev/null +++ b/src/onebusaway/types/stops_for_location_list_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = ["StopsForLocationListResponse", "StopsForLocationListResponseData", "StopsForLocationListResponseDataList"] + + +class StopsForLocationListResponseDataList(BaseModel): + id: str + + lat: float + + location_type: int = FieldInfo(alias="locationType") + + lon: float + + name: str + + parent: str + + route_ids: List[str] = FieldInfo(alias="routeIds") + + static_route_ids: List[str] = FieldInfo(alias="staticRouteIds") + + code: Optional[str] = None + + direction: Optional[str] = None + + wheelchair_boarding: Optional[str] = FieldInfo(alias="wheelchairBoarding", default=None) + + +class StopsForLocationListResponseData(BaseModel): + list: List[StopsForLocationListResponseDataList] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + out_of_range: Optional[bool] = FieldInfo(alias="outOfRange", default=None) + + +class StopsForLocationListResponse(ResponseWrapper): + data: StopsForLocationListResponseData diff --git a/src/onebusaway/types/stops_for_location_retrieve_params.py b/src/onebusaway/types/stops_for_location_retrieve_params.py deleted file mode 100644 index b5c1ba6e..00000000 --- a/src/onebusaway/types/stops_for_location_retrieve_params.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["StopsForLocationRetrieveParams"] - - -class StopsForLocationRetrieveParams(TypedDict, total=False): - lat: float - - lon: float diff --git a/src/onebusaway/types/stops_for_route_list_response.py b/src/onebusaway/types/stops_for_route_list_response.py index 2aa16448..53cff4e6 100644 --- a/src/onebusaway/types/stops_for_route_list_response.py +++ b/src/onebusaway/types/stops_for_route_list_response.py @@ -66,9 +66,9 @@ class StopsForRouteListResponseDataEntry(BaseModel): class StopsForRouteListResponseData(BaseModel): - entry: Optional[StopsForRouteListResponseDataEntry] = None + entry: StopsForRouteListResponseDataEntry - references: Optional[References] = None + references: References class StopsForRouteListResponse(ResponseWrapper): diff --git a/src/onebusaway/types/trip_detail_retrieve_response.py b/src/onebusaway/types/trip_detail_retrieve_response.py index 24f2152c..ccb12cc6 100644 --- a/src/onebusaway/types/trip_detail_retrieve_response.py +++ b/src/onebusaway/types/trip_detail_retrieve_response.py @@ -35,20 +35,20 @@ class TripDetailRetrieveResponseDataEntryScheduleStopTime(BaseModel): class TripDetailRetrieveResponseDataEntrySchedule(BaseModel): - frequency: Optional[str] = None + next_trip_id: str = FieldInfo(alias="nextTripId") - next_trip_id: Optional[str] = FieldInfo(alias="nextTripId", default=None) + previous_trip_id: str = FieldInfo(alias="previousTripId") - previous_trip_id: Optional[str] = FieldInfo(alias="previousTripId", default=None) + stop_times: List[TripDetailRetrieveResponseDataEntryScheduleStopTime] = FieldInfo(alias="stopTimes") - stop_times: Optional[List[TripDetailRetrieveResponseDataEntryScheduleStopTime]] = FieldInfo( - alias="stopTimes", default=None - ) + time_zone: str = FieldInfo(alias="timeZone") - time_zone: Optional[str] = FieldInfo(alias="timeZone", default=None) + frequency: Optional[str] = None class TripDetailRetrieveResponseDataEntryStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + lat: Optional[float] = None """Latitude of the last known location of the transit vehicle.""" @@ -57,6 +57,8 @@ class TripDetailRetrieveResponseDataEntryStatusLastKnownLocation(BaseModel): class TripDetailRetrieveResponseDataEntryStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + lat: Optional[float] = None """Latitude of the current position of the transit vehicle.""" @@ -65,47 +67,77 @@ class TripDetailRetrieveResponseDataEntryStatusPosition(BaseModel): class TripDetailRetrieveResponseDataEntryStatus(BaseModel): - active_trip_id: Optional[str] = FieldInfo(alias="activeTripId", default=None) + active_trip_id: str = FieldInfo(alias="activeTripId") """Trip ID of the trip the vehicle is actively serving.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of the active trip into the sequence of trips for the active block.""" - closest_stop: Optional[str] = FieldInfo(alias="closestStop", default=None) + closest_stop: str = FieldInfo(alias="closestStop") """ID of the closest stop to the current location of the transit vehicle.""" + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) """ Time offset from the closest stop to the current position of the transit vehicle (in seconds). """ - distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) - """Distance, in meters, the transit vehicle has progressed along the active trip.""" - frequency: Optional[str] = None """Information about frequency-based scheduling, if applicable to the trip.""" - last_known_distance_along_trip: Optional[float] = FieldInfo(alias="lastKnownDistanceAlongTrip", default=None) - """ - Last known distance along the trip received in real-time from the transit - vehicle. - """ - last_known_location: Optional[TripDetailRetrieveResponseDataEntryStatusLastKnownLocation] = FieldInfo( alias="lastKnownLocation", default=None ) - """Last known location of the transit vehicle.""" + """Last known location of the transit vehicle (optional).""" last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) """Last known orientation value received in real-time from the transit vehicle.""" - last_location_update_time: Optional[int] = FieldInfo(alias="lastLocationUpdateTime", default=None) - """Timestamp of the last known real-time location update from the transit vehicle.""" - - last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) - """Timestamp of the last known real-time update from the transit vehicle.""" - next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) """ID of the next stop the transit vehicle is scheduled to arrive at.""" @@ -115,56 +147,28 @@ class TripDetailRetrieveResponseDataEntryStatus(BaseModel): (in seconds). """ - occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) - """Capacity of the transit vehicle in terms of occupancy.""" - - occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) - """Current count of occupants in the transit vehicle.""" - - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) - """Current occupancy status of the transit vehicle.""" - orientation: Optional[float] = None """Orientation of the transit vehicle, represented as an angle in degrees.""" - phase: Optional[str] = None - """Current journey phase of the trip.""" - position: Optional[TripDetailRetrieveResponseDataEntryStatusPosition] = None """Current position of the transit vehicle.""" - predicted: Optional[bool] = None - """Indicates if real-time arrival info is available for this trip.""" - scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) """ Distance, in meters, the transit vehicle is scheduled to have progressed along the active trip. """ - schedule_deviation: Optional[int] = FieldInfo(alias="scheduleDeviation", default=None) - """Deviation from the schedule in seconds (positive for late, negative for early).""" - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this trip.""" - status: Optional[str] = None - """Current status modifiers for the trip.""" - - total_distance_along_trip: Optional[float] = FieldInfo(alias="totalDistanceAlongTrip", default=None) - """Total length of the trip, in meters.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) """ID of the transit vehicle currently serving the trip.""" class TripDetailRetrieveResponseDataEntry(BaseModel): + trip_id: str = FieldInfo(alias="tripId") + frequency: Optional[str] = None schedule: Optional[TripDetailRetrieveResponseDataEntrySchedule] = None @@ -175,8 +179,6 @@ class TripDetailRetrieveResponseDataEntry(BaseModel): status: Optional[TripDetailRetrieveResponseDataEntryStatus] = None - trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) - class TripDetailRetrieveResponseData(BaseModel): entry: TripDetailRetrieveResponseDataEntry @@ -185,4 +187,4 @@ class TripDetailRetrieveResponseData(BaseModel): class TripDetailRetrieveResponse(ResponseWrapper): - data: Optional[TripDetailRetrieveResponseData] = None + data: TripDetailRetrieveResponseData diff --git a/src/onebusaway/types/trip_for_vehicle_retrieve_response.py b/src/onebusaway/types/trip_for_vehicle_retrieve_response.py index 90a6ff86..d7f7ee95 100644 --- a/src/onebusaway/types/trip_for_vehicle_retrieve_response.py +++ b/src/onebusaway/types/trip_for_vehicle_retrieve_response.py @@ -35,20 +35,20 @@ class TripForVehicleRetrieveResponseDataEntryScheduleStopTime(BaseModel): class TripForVehicleRetrieveResponseDataEntrySchedule(BaseModel): - frequency: Optional[str] = None + next_trip_id: str = FieldInfo(alias="nextTripId") - next_trip_id: Optional[str] = FieldInfo(alias="nextTripId", default=None) + previous_trip_id: str = FieldInfo(alias="previousTripId") - previous_trip_id: Optional[str] = FieldInfo(alias="previousTripId", default=None) + stop_times: List[TripForVehicleRetrieveResponseDataEntryScheduleStopTime] = FieldInfo(alias="stopTimes") - stop_times: Optional[List[TripForVehicleRetrieveResponseDataEntryScheduleStopTime]] = FieldInfo( - alias="stopTimes", default=None - ) + time_zone: str = FieldInfo(alias="timeZone") - time_zone: Optional[str] = FieldInfo(alias="timeZone", default=None) + frequency: Optional[str] = None class TripForVehicleRetrieveResponseDataEntryStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + lat: Optional[float] = None """Latitude of the last known location of the transit vehicle.""" @@ -57,6 +57,8 @@ class TripForVehicleRetrieveResponseDataEntryStatusLastKnownLocation(BaseModel): class TripForVehicleRetrieveResponseDataEntryStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + lat: Optional[float] = None """Latitude of the current position of the transit vehicle.""" @@ -65,47 +67,77 @@ class TripForVehicleRetrieveResponseDataEntryStatusPosition(BaseModel): class TripForVehicleRetrieveResponseDataEntryStatus(BaseModel): - active_trip_id: Optional[str] = FieldInfo(alias="activeTripId", default=None) + active_trip_id: str = FieldInfo(alias="activeTripId") """Trip ID of the trip the vehicle is actively serving.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of the active trip into the sequence of trips for the active block.""" - closest_stop: Optional[str] = FieldInfo(alias="closestStop", default=None) + closest_stop: str = FieldInfo(alias="closestStop") """ID of the closest stop to the current location of the transit vehicle.""" + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) """ Time offset from the closest stop to the current position of the transit vehicle (in seconds). """ - distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) - """Distance, in meters, the transit vehicle has progressed along the active trip.""" - frequency: Optional[str] = None """Information about frequency-based scheduling, if applicable to the trip.""" - last_known_distance_along_trip: Optional[float] = FieldInfo(alias="lastKnownDistanceAlongTrip", default=None) - """ - Last known distance along the trip received in real-time from the transit - vehicle. - """ - last_known_location: Optional[TripForVehicleRetrieveResponseDataEntryStatusLastKnownLocation] = FieldInfo( alias="lastKnownLocation", default=None ) - """Last known location of the transit vehicle.""" + """Last known location of the transit vehicle (optional).""" last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) """Last known orientation value received in real-time from the transit vehicle.""" - last_location_update_time: Optional[int] = FieldInfo(alias="lastLocationUpdateTime", default=None) - """Timestamp of the last known real-time location update from the transit vehicle.""" - - last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) - """Timestamp of the last known real-time update from the transit vehicle.""" - next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) """ID of the next stop the transit vehicle is scheduled to arrive at.""" @@ -115,56 +147,28 @@ class TripForVehicleRetrieveResponseDataEntryStatus(BaseModel): (in seconds). """ - occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) - """Capacity of the transit vehicle in terms of occupancy.""" - - occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) - """Current count of occupants in the transit vehicle.""" - - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) - """Current occupancy status of the transit vehicle.""" - orientation: Optional[float] = None """Orientation of the transit vehicle, represented as an angle in degrees.""" - phase: Optional[str] = None - """Current journey phase of the trip.""" - position: Optional[TripForVehicleRetrieveResponseDataEntryStatusPosition] = None """Current position of the transit vehicle.""" - predicted: Optional[bool] = None - """Indicates if real-time arrival info is available for this trip.""" - scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) """ Distance, in meters, the transit vehicle is scheduled to have progressed along the active trip. """ - schedule_deviation: Optional[int] = FieldInfo(alias="scheduleDeviation", default=None) - """Deviation from the schedule in seconds (positive for late, negative for early).""" - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this trip.""" - status: Optional[str] = None - """Current status modifiers for the trip.""" - - total_distance_along_trip: Optional[float] = FieldInfo(alias="totalDistanceAlongTrip", default=None) - """Total length of the trip, in meters.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) """ID of the transit vehicle currently serving the trip.""" class TripForVehicleRetrieveResponseDataEntry(BaseModel): + trip_id: str = FieldInfo(alias="tripId") + frequency: Optional[str] = None schedule: Optional[TripForVehicleRetrieveResponseDataEntrySchedule] = None @@ -175,8 +179,6 @@ class TripForVehicleRetrieveResponseDataEntry(BaseModel): status: Optional[TripForVehicleRetrieveResponseDataEntryStatus] = None - trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) - class TripForVehicleRetrieveResponseData(BaseModel): entry: TripForVehicleRetrieveResponseDataEntry @@ -185,4 +187,4 @@ class TripForVehicleRetrieveResponseData(BaseModel): class TripForVehicleRetrieveResponse(ResponseWrapper): - data: Optional[TripForVehicleRetrieveResponseData] = None + data: TripForVehicleRetrieveResponseData diff --git a/src/onebusaway/types/trip_retrieve_response.py b/src/onebusaway/types/trip_retrieve_response.py index 92c8546b..152bfa25 100644 --- a/src/onebusaway/types/trip_retrieve_response.py +++ b/src/onebusaway/types/trip_retrieve_response.py @@ -16,6 +16,8 @@ class TripRetrieveResponseDataEntry(BaseModel): route_id: str = FieldInfo(alias="routeId") + service_id: str = FieldInfo(alias="serviceId") + block_id: Optional[str] = FieldInfo(alias="blockId", default=None) direction_id: Optional[str] = FieldInfo(alias="directionId", default=None) @@ -24,8 +26,6 @@ class TripRetrieveResponseDataEntry(BaseModel): route_short_name: Optional[str] = FieldInfo(alias="routeShortName", default=None) - service_id: Optional[str] = FieldInfo(alias="serviceId", default=None) - shape_id: Optional[str] = FieldInfo(alias="shapeId", default=None) time_zone: Optional[str] = FieldInfo(alias="timeZone", default=None) @@ -42,4 +42,4 @@ class TripRetrieveResponseData(BaseModel): class TripRetrieveResponse(ResponseWrapper): - data: Optional[TripRetrieveResponseData] = None + data: TripRetrieveResponseData diff --git a/src/onebusaway/types/trips_for_location_retrieve_params.py b/src/onebusaway/types/trips_for_location_list_params.py similarity index 90% rename from src/onebusaway/types/trips_for_location_retrieve_params.py rename to src/onebusaway/types/trips_for_location_list_params.py index 341a0fd1..69a91863 100644 --- a/src/onebusaway/types/trips_for_location_retrieve_params.py +++ b/src/onebusaway/types/trips_for_location_list_params.py @@ -6,10 +6,10 @@ from .._utils import PropertyInfo -__all__ = ["TripsForLocationRetrieveParams"] +__all__ = ["TripsForLocationListParams"] -class TripsForLocationRetrieveParams(TypedDict, total=False): +class TripsForLocationListParams(TypedDict, total=False): lat: Required[float] """The latitude coordinate of the search center""" diff --git a/src/onebusaway/types/trips_for_location_list_response.py b/src/onebusaway/types/trips_for_location_list_response.py new file mode 100644 index 00000000..9530952c --- /dev/null +++ b/src/onebusaway/types/trips_for_location_list_response.py @@ -0,0 +1,196 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "TripsForLocationListResponse", + "TripsForLocationListResponseData", + "TripsForLocationListResponseDataList", + "TripsForLocationListResponseDataListSchedule", + "TripsForLocationListResponseDataListScheduleStopTime", + "TripsForLocationListResponseDataListStatus", + "TripsForLocationListResponseDataListStatusLastKnownLocation", + "TripsForLocationListResponseDataListStatusPosition", +] + + +class TripsForLocationListResponseDataListScheduleStopTime(BaseModel): + arrival_time: Optional[int] = FieldInfo(alias="arrivalTime", default=None) + + departure_time: Optional[int] = FieldInfo(alias="departureTime", default=None) + + distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) + + historical_occupancy: Optional[str] = FieldInfo(alias="historicalOccupancy", default=None) + + stop_headsign: Optional[str] = FieldInfo(alias="stopHeadsign", default=None) + + stop_id: Optional[str] = FieldInfo(alias="stopId", default=None) + + +class TripsForLocationListResponseDataListSchedule(BaseModel): + next_trip_id: str = FieldInfo(alias="nextTripId") + + previous_trip_id: str = FieldInfo(alias="previousTripId") + + stop_times: List[TripsForLocationListResponseDataListScheduleStopTime] = FieldInfo(alias="stopTimes") + + time_zone: str = FieldInfo(alias="timeZone") + + frequency: Optional[str] = None + + +class TripsForLocationListResponseDataListStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + + lat: Optional[float] = None + """Latitude of the last known location of the transit vehicle.""" + + lon: Optional[float] = None + """Longitude of the last known location of the transit vehicle.""" + + +class TripsForLocationListResponseDataListStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + + lat: Optional[float] = None + """Latitude of the current position of the transit vehicle.""" + + lon: Optional[float] = None + """Longitude of the current position of the transit vehicle.""" + + +class TripsForLocationListResponseDataListStatus(BaseModel): + active_trip_id: str = FieldInfo(alias="activeTripId") + """Trip ID of the trip the vehicle is actively serving.""" + + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") + """Index of the active trip into the sequence of trips for the active block.""" + + closest_stop: str = FieldInfo(alias="closestStop") + """ID of the closest stop to the current location of the transit vehicle.""" + + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) + """ + Time offset from the closest stop to the current position of the transit vehicle + (in seconds). + """ + + frequency: Optional[str] = None + """Information about frequency-based scheduling, if applicable to the trip.""" + + last_known_location: Optional[TripsForLocationListResponseDataListStatusLastKnownLocation] = FieldInfo( + alias="lastKnownLocation", default=None + ) + """Last known location of the transit vehicle (optional).""" + + last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) + """Last known orientation value received in real-time from the transit vehicle.""" + + next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) + """ID of the next stop the transit vehicle is scheduled to arrive at.""" + + next_stop_time_offset: Optional[int] = FieldInfo(alias="nextStopTimeOffset", default=None) + """ + Time offset from the next stop to the current position of the transit vehicle + (in seconds). + """ + + orientation: Optional[float] = None + """Orientation of the transit vehicle, represented as an angle in degrees.""" + + position: Optional[TripsForLocationListResponseDataListStatusPosition] = None + """Current position of the transit vehicle.""" + + scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) + """ + Distance, in meters, the transit vehicle is scheduled to have progressed along + the active trip. + """ + + situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) + """References to situation elements (if any) applicable to this trip.""" + + vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) + """ID of the transit vehicle currently serving the trip.""" + + +class TripsForLocationListResponseDataList(BaseModel): + schedule: TripsForLocationListResponseDataListSchedule + + status: TripsForLocationListResponseDataListStatus + + trip_id: str = FieldInfo(alias="tripId") + + frequency: Optional[str] = None + + service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) + + situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) + + +class TripsForLocationListResponseData(BaseModel): + list: List[TripsForLocationListResponseDataList] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + """Indicates if the limit of trips has been exceeded""" + + out_of_range: Optional[bool] = FieldInfo(alias="outOfRange", default=None) + """Indicates if the search location is out of range""" + + +class TripsForLocationListResponse(ResponseWrapper): + data: TripsForLocationListResponseData diff --git a/src/onebusaway/types/trips_for_location_retrieve_response.py b/src/onebusaway/types/trips_for_location_retrieve_response.py deleted file mode 100644 index 237c30c2..00000000 --- a/src/onebusaway/types/trips_for_location_retrieve_response.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .shared.references import References -from .shared.response_wrapper import ResponseWrapper - -__all__ = [ - "TripsForLocationRetrieveResponse", - "TripsForLocationRetrieveResponseData", - "TripsForLocationRetrieveResponseDataList", -] - - -class TripsForLocationRetrieveResponseDataList(BaseModel): - frequency: Optional[str] = None - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) - - trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) - - -class TripsForLocationRetrieveResponseData(BaseModel): - list: List[TripsForLocationRetrieveResponseDataList] - - references: References - - limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) - """Indicates if the limit of trips has been exceeded""" - - out_of_range: Optional[bool] = FieldInfo(alias="outOfRange", default=None) - """Indicates if the search location is out of range""" - - -class TripsForLocationRetrieveResponse(ResponseWrapper): - data: Optional[TripsForLocationRetrieveResponseData] = None diff --git a/src/onebusaway/types/trips_for_route_list_params.py b/src/onebusaway/types/trips_for_route_list_params.py new file mode 100644 index 00000000..c2f7ec01 --- /dev/null +++ b/src/onebusaway/types/trips_for_route_list_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["TripsForRouteListParams"] + + +class TripsForRouteListParams(TypedDict, total=False): + include_schedule: Annotated[bool, PropertyInfo(alias="includeSchedule")] + """Determine whether full schedule elements are included. Defaults to false.""" + + include_status: Annotated[bool, PropertyInfo(alias="includeStatus")] + """ + Determine whether full tripStatus elements with real-time information are + included. Defaults to false. + """ + + time: int + """Query the system at a specific time. Useful for testing.""" diff --git a/src/onebusaway/types/trips_for_route_list_response.py b/src/onebusaway/types/trips_for_route_list_response.py new file mode 100644 index 00000000..e7ecb00d --- /dev/null +++ b/src/onebusaway/types/trips_for_route_list_response.py @@ -0,0 +1,192 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.references import References +from .shared.response_wrapper import ResponseWrapper + +__all__ = [ + "TripsForRouteListResponse", + "TripsForRouteListResponseData", + "TripsForRouteListResponseDataList", + "TripsForRouteListResponseDataListSchedule", + "TripsForRouteListResponseDataListScheduleStopTime", + "TripsForRouteListResponseDataListStatus", + "TripsForRouteListResponseDataListStatusLastKnownLocation", + "TripsForRouteListResponseDataListStatusPosition", +] + + +class TripsForRouteListResponseDataListScheduleStopTime(BaseModel): + arrival_time: Optional[int] = FieldInfo(alias="arrivalTime", default=None) + + departure_time: Optional[int] = FieldInfo(alias="departureTime", default=None) + + distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) + + historical_occupancy: Optional[str] = FieldInfo(alias="historicalOccupancy", default=None) + + stop_headsign: Optional[str] = FieldInfo(alias="stopHeadsign", default=None) + + stop_id: Optional[str] = FieldInfo(alias="stopId", default=None) + + +class TripsForRouteListResponseDataListSchedule(BaseModel): + next_trip_id: str = FieldInfo(alias="nextTripId") + + previous_trip_id: str = FieldInfo(alias="previousTripId") + + stop_times: List[TripsForRouteListResponseDataListScheduleStopTime] = FieldInfo(alias="stopTimes") + + time_zone: str = FieldInfo(alias="timeZone") + + frequency: Optional[str] = None + + +class TripsForRouteListResponseDataListStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + + lat: Optional[float] = None + """Latitude of the last known location of the transit vehicle.""" + + lon: Optional[float] = None + """Longitude of the last known location of the transit vehicle.""" + + +class TripsForRouteListResponseDataListStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + + lat: Optional[float] = None + """Latitude of the current position of the transit vehicle.""" + + lon: Optional[float] = None + """Longitude of the current position of the transit vehicle.""" + + +class TripsForRouteListResponseDataListStatus(BaseModel): + active_trip_id: str = FieldInfo(alias="activeTripId") + """Trip ID of the trip the vehicle is actively serving.""" + + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") + """Index of the active trip into the sequence of trips for the active block.""" + + closest_stop: str = FieldInfo(alias="closestStop") + """ID of the closest stop to the current location of the transit vehicle.""" + + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) + """ + Time offset from the closest stop to the current position of the transit vehicle + (in seconds). + """ + + frequency: Optional[str] = None + """Information about frequency-based scheduling, if applicable to the trip.""" + + last_known_location: Optional[TripsForRouteListResponseDataListStatusLastKnownLocation] = FieldInfo( + alias="lastKnownLocation", default=None + ) + """Last known location of the transit vehicle (optional).""" + + last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) + """Last known orientation value received in real-time from the transit vehicle.""" + + next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) + """ID of the next stop the transit vehicle is scheduled to arrive at.""" + + next_stop_time_offset: Optional[int] = FieldInfo(alias="nextStopTimeOffset", default=None) + """ + Time offset from the next stop to the current position of the transit vehicle + (in seconds). + """ + + orientation: Optional[float] = None + """Orientation of the transit vehicle, represented as an angle in degrees.""" + + position: Optional[TripsForRouteListResponseDataListStatusPosition] = None + """Current position of the transit vehicle.""" + + scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) + """ + Distance, in meters, the transit vehicle is scheduled to have progressed along + the active trip. + """ + + situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) + """References to situation elements (if any) applicable to this trip.""" + + vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) + """ID of the transit vehicle currently serving the trip.""" + + +class TripsForRouteListResponseDataList(BaseModel): + schedule: TripsForRouteListResponseDataListSchedule + + status: TripsForRouteListResponseDataListStatus + + trip_id: str = FieldInfo(alias="tripId") + + frequency: Optional[str] = None + + service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) + + situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) + + +class TripsForRouteListResponseData(BaseModel): + list: List[TripsForRouteListResponseDataList] + + references: References + + limit_exceeded: Optional[bool] = FieldInfo(alias="limitExceeded", default=None) + + +class TripsForRouteListResponse(ResponseWrapper): + data: TripsForRouteListResponseData diff --git a/src/onebusaway/types/vehicles_for_agency_list_response.py b/src/onebusaway/types/vehicles_for_agency_list_response.py index f61596dd..0cb3e147 100644 --- a/src/onebusaway/types/vehicles_for_agency_list_response.py +++ b/src/onebusaway/types/vehicles_for_agency_list_response.py @@ -26,6 +26,8 @@ class VehiclesForAgencyListResponseDataListLocation(BaseModel): class VehiclesForAgencyListResponseDataListTripStatusLastKnownLocation(BaseModel): + """Last known location of the transit vehicle (optional).""" + lat: Optional[float] = None """Latitude of the last known location of the transit vehicle.""" @@ -34,6 +36,8 @@ class VehiclesForAgencyListResponseDataListTripStatusLastKnownLocation(BaseModel class VehiclesForAgencyListResponseDataListTripStatusPosition(BaseModel): + """Current position of the transit vehicle.""" + lat: Optional[float] = None """Latitude of the current position of the transit vehicle.""" @@ -42,47 +46,77 @@ class VehiclesForAgencyListResponseDataListTripStatusPosition(BaseModel): class VehiclesForAgencyListResponseDataListTripStatus(BaseModel): - active_trip_id: Optional[str] = FieldInfo(alias="activeTripId", default=None) + active_trip_id: str = FieldInfo(alias="activeTripId") """Trip ID of the trip the vehicle is actively serving.""" - block_trip_sequence: Optional[int] = FieldInfo(alias="blockTripSequence", default=None) + block_trip_sequence: int = FieldInfo(alias="blockTripSequence") """Index of the active trip into the sequence of trips for the active block.""" - closest_stop: Optional[str] = FieldInfo(alias="closestStop", default=None) + closest_stop: str = FieldInfo(alias="closestStop") """ID of the closest stop to the current location of the transit vehicle.""" + distance_along_trip: float = FieldInfo(alias="distanceAlongTrip") + """Distance, in meters, the transit vehicle has progressed along the active trip.""" + + last_known_distance_along_trip: float = FieldInfo(alias="lastKnownDistanceAlongTrip") + """ + Last known distance along the trip received in real-time from the transit + vehicle. + """ + + last_location_update_time: int = FieldInfo(alias="lastLocationUpdateTime") + """Timestamp of the last known real-time location update from the transit vehicle.""" + + last_update_time: int = FieldInfo(alias="lastUpdateTime") + """Timestamp of the last known real-time update from the transit vehicle.""" + + occupancy_capacity: int = FieldInfo(alias="occupancyCapacity") + """Capacity of the transit vehicle in terms of occupancy.""" + + occupancy_count: int = FieldInfo(alias="occupancyCount") + """Current count of occupants in the transit vehicle.""" + + occupancy_status: str = FieldInfo(alias="occupancyStatus") + """Current occupancy status of the transit vehicle.""" + + phase: str + """Current journey phase of the trip.""" + + predicted: bool + """Indicates if real-time arrival info is available for this trip.""" + + schedule_deviation: int = FieldInfo(alias="scheduleDeviation") + """Deviation from the schedule in seconds (positive for late, negative for early).""" + + service_date: int = FieldInfo(alias="serviceDate") + """ + Time, in milliseconds since the Unix epoch, of midnight for the start of the + service date for the trip. + """ + + status: str + """Current status modifiers for the trip.""" + + total_distance_along_trip: float = FieldInfo(alias="totalDistanceAlongTrip") + """Total length of the trip, in meters.""" + closest_stop_time_offset: Optional[int] = FieldInfo(alias="closestStopTimeOffset", default=None) """ Time offset from the closest stop to the current position of the transit vehicle (in seconds). """ - distance_along_trip: Optional[float] = FieldInfo(alias="distanceAlongTrip", default=None) - """Distance, in meters, the transit vehicle has progressed along the active trip.""" - frequency: Optional[str] = None """Information about frequency-based scheduling, if applicable to the trip.""" - last_known_distance_along_trip: Optional[float] = FieldInfo(alias="lastKnownDistanceAlongTrip", default=None) - """ - Last known distance along the trip received in real-time from the transit - vehicle. - """ - last_known_location: Optional[VehiclesForAgencyListResponseDataListTripStatusLastKnownLocation] = FieldInfo( alias="lastKnownLocation", default=None ) - """Last known location of the transit vehicle.""" + """Last known location of the transit vehicle (optional).""" last_known_orientation: Optional[float] = FieldInfo(alias="lastKnownOrientation", default=None) """Last known orientation value received in real-time from the transit vehicle.""" - last_location_update_time: Optional[int] = FieldInfo(alias="lastLocationUpdateTime", default=None) - """Timestamp of the last known real-time location update from the transit vehicle.""" - - last_update_time: Optional[int] = FieldInfo(alias="lastUpdateTime", default=None) - """Timestamp of the last known real-time update from the transit vehicle.""" - next_stop: Optional[str] = FieldInfo(alias="nextStop", default=None) """ID of the next stop the transit vehicle is scheduled to arrive at.""" @@ -92,51 +126,21 @@ class VehiclesForAgencyListResponseDataListTripStatus(BaseModel): (in seconds). """ - occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) - """Capacity of the transit vehicle in terms of occupancy.""" - - occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) - """Current count of occupants in the transit vehicle.""" - - occupancy_status: Optional[str] = FieldInfo(alias="occupancyStatus", default=None) - """Current occupancy status of the transit vehicle.""" - orientation: Optional[float] = None """Orientation of the transit vehicle, represented as an angle in degrees.""" - phase: Optional[str] = None - """Current journey phase of the trip.""" - position: Optional[VehiclesForAgencyListResponseDataListTripStatusPosition] = None """Current position of the transit vehicle.""" - predicted: Optional[bool] = None - """Indicates if real-time arrival info is available for this trip.""" - scheduled_distance_along_trip: Optional[float] = FieldInfo(alias="scheduledDistanceAlongTrip", default=None) """ Distance, in meters, the transit vehicle is scheduled to have progressed along the active trip. """ - schedule_deviation: Optional[int] = FieldInfo(alias="scheduleDeviation", default=None) - """Deviation from the schedule in seconds (positive for late, negative for early).""" - - service_date: Optional[int] = FieldInfo(alias="serviceDate", default=None) - """ - Time, in milliseconds since the Unix epoch, of midnight for the start of the - service date for the trip. - """ - situation_ids: Optional[List[str]] = FieldInfo(alias="situationIds", default=None) """References to situation elements (if any) applicable to this trip.""" - status: Optional[str] = None - """Current status modifiers for the trip.""" - - total_distance_along_trip: Optional[float] = FieldInfo(alias="totalDistanceAlongTrip", default=None) - """Total length of the trip, in meters.""" - vehicle_id: Optional[str] = FieldInfo(alias="vehicleId", default=None) """ID of the transit vehicle currently serving the trip.""" @@ -146,14 +150,10 @@ class VehiclesForAgencyListResponseDataList(BaseModel): last_update_time: int = FieldInfo(alias="lastUpdateTime") - location: VehiclesForAgencyListResponseDataListLocation - - trip_id: str = FieldInfo(alias="tripId") - - trip_status: VehiclesForAgencyListResponseDataListTripStatus = FieldInfo(alias="tripStatus") - vehicle_id: str = FieldInfo(alias="vehicleId") + location: Optional[VehiclesForAgencyListResponseDataListLocation] = None + occupancy_capacity: Optional[int] = FieldInfo(alias="occupancyCapacity", default=None) occupancy_count: Optional[int] = FieldInfo(alias="occupancyCount", default=None) @@ -164,6 +164,10 @@ class VehiclesForAgencyListResponseDataList(BaseModel): status: Optional[str] = None + trip_id: Optional[str] = FieldInfo(alias="tripId", default=None) + + trip_status: Optional[VehiclesForAgencyListResponseDataListTripStatus] = FieldInfo(alias="tripStatus", default=None) + class VehiclesForAgencyListResponseData(BaseModel): list: List[VehiclesForAgencyListResponseDataList] @@ -174,4 +178,4 @@ class VehiclesForAgencyListResponseData(BaseModel): class VehiclesForAgencyListResponse(ResponseWrapper): - data: Optional[VehiclesForAgencyListResponseData] = None + data: VehiclesForAgencyListResponseData diff --git a/tests/api_resources/test_agencies_with_coverage.py b/tests/api_resources/test_agencies_with_coverage.py index fd969823..b98f23a1 100644 --- a/tests/api_resources/test_agencies_with_coverage.py +++ b/tests/api_resources/test_agencies_with_coverage.py @@ -9,7 +9,7 @@ from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK from tests.utils import assert_matches_type -from onebusaway.types import AgenciesWithCoverageRetrieveResponse +from onebusaway.types import AgenciesWithCoverageListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,55 +18,57 @@ class TestAgenciesWithCoverage: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: OnebusawaySDK) -> None: - agencies_with_coverage = client.agencies_with_coverage.retrieve() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + def test_method_list(self, client: OnebusawaySDK) -> None: + agencies_with_coverage = client.agencies_with_coverage.list() + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: - response = client.agencies_with_coverage.with_raw_response.retrieve() + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.agencies_with_coverage.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" agencies_with_coverage = response.parse() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: - with client.agencies_with_coverage.with_streaming_response.retrieve() as response: + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.agencies_with_coverage.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" agencies_with_coverage = response.parse() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) assert cast(Any, response.is_closed) is True class TestAsyncAgenciesWithCoverage: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize - async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - agencies_with_coverage = await async_client.agencies_with_coverage.retrieve() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + agencies_with_coverage = await async_client.agencies_with_coverage.list() + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - response = await async_client.agencies_with_coverage.with_raw_response.retrieve() + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.agencies_with_coverage.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" agencies_with_coverage = await response.parse() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - async with async_client.agencies_with_coverage.with_streaming_response.retrieve() as response: + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.agencies_with_coverage.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" agencies_with_coverage = await response.parse() - assert_matches_type(AgenciesWithCoverageRetrieveResponse, agencies_with_coverage, path=["response"]) + assert_matches_type(AgenciesWithCoverageListResponse, agencies_with_coverage, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_agency.py b/tests/api_resources/test_agency.py index 05b83a8d..d296efc3 100644 --- a/tests/api_resources/test_agency.py +++ b/tests/api_resources/test_agency.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_arrival_and_departure.py b/tests/api_resources/test_arrival_and_departure.py index 0ba81096..88c379c4 100644 --- a/tests/api_resources/test_arrival_and_departure.py +++ b/tests/api_resources/test_arrival_and_departure.py @@ -129,7 +129,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncArrivalAndDeparture: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_block.py b/tests/api_resources/test_block.py new file mode 100644 index 00000000..86ba5c1c --- /dev/null +++ b/tests/api_resources/test_block.py @@ -0,0 +1,100 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import BlockRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBlock: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OnebusawaySDK) -> None: + block = client.block.retrieve( + "blockID", + ) + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: + response = client.block.with_raw_response.retrieve( + "blockID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + block = response.parse() + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: + with client.block.with_streaming_response.retrieve( + "blockID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + block = response.parse() + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + client.block.with_raw_response.retrieve( + "", + ) + + +class TestAsyncBlock: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + block = await async_client.block.retrieve( + "blockID", + ) + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.block.with_raw_response.retrieve( + "blockID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + block = await response.parse() + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.block.with_streaming_response.retrieve( + "blockID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + block = await response.parse() + assert_matches_type(BlockRetrieveResponse, block, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `block_id` but received ''"): + await async_client.block.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/test_config.py b/tests/api_resources/test_config.py index 62bdd913..9b19317a 100644 --- a/tests/api_resources/test_config.py +++ b/tests/api_resources/test_config.py @@ -44,7 +44,9 @@ def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncConfig: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_current_time.py b/tests/api_resources/test_current_time.py index 1a055286..39a3b20c 100644 --- a/tests/api_resources/test_current_time.py +++ b/tests/api_resources/test_current_time.py @@ -44,7 +44,9 @@ def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncCurrentTime: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_report_problem_with_stop.py b/tests/api_resources/test_report_problem_with_stop.py new file mode 100644 index 00000000..ce177c06 --- /dev/null +++ b/tests/api_resources/test_report_problem_with_stop.py @@ -0,0 +1,124 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types.shared import ResponseWrapper + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestReportProblemWithStop: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OnebusawaySDK) -> None: + report_problem_with_stop = client.report_problem_with_stop.retrieve( + stop_id="stopID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: + report_problem_with_stop = client.report_problem_with_stop.retrieve( + stop_id="stopID", + code="stop_name_wrong", + user_comment="userComment", + user_lat=0, + user_location_accuracy=0, + user_lon=0, + ) + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: + response = client.report_problem_with_stop.with_raw_response.retrieve( + stop_id="stopID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + report_problem_with_stop = response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: + with client.report_problem_with_stop.with_streaming_response.retrieve( + stop_id="stopID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + report_problem_with_stop = response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `stop_id` but received ''"): + client.report_problem_with_stop.with_raw_response.retrieve( + stop_id="", + ) + + +class TestAsyncReportProblemWithStop: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + report_problem_with_stop = await async_client.report_problem_with_stop.retrieve( + stop_id="stopID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + report_problem_with_stop = await async_client.report_problem_with_stop.retrieve( + stop_id="stopID", + code="stop_name_wrong", + user_comment="userComment", + user_lat=0, + user_location_accuracy=0, + user_lon=0, + ) + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.report_problem_with_stop.with_raw_response.retrieve( + stop_id="stopID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + report_problem_with_stop = await response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.report_problem_with_stop.with_streaming_response.retrieve( + stop_id="stopID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + report_problem_with_stop = await response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_stop, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `stop_id` but received ''"): + await async_client.report_problem_with_stop.with_raw_response.retrieve( + stop_id="", + ) diff --git a/tests/api_resources/test_report_problem_with_trip.py b/tests/api_resources/test_report_problem_with_trip.py new file mode 100644 index 00000000..caa07304 --- /dev/null +++ b/tests/api_resources/test_report_problem_with_trip.py @@ -0,0 +1,134 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types.shared import ResponseWrapper + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestReportProblemWithTrip: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OnebusawaySDK) -> None: + report_problem_with_trip = client.report_problem_with_trip.retrieve( + trip_id="tripID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: + report_problem_with_trip = client.report_problem_with_trip.retrieve( + trip_id="tripID", + code="vehicle_never_came", + service_date=0, + stop_id="stopID", + user_comment="userComment", + user_lat=0, + user_location_accuracy=0, + user_lon=0, + user_on_vehicle=True, + user_vehicle_number="userVehicleNumber", + vehicle_id="vehicleID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: + response = client.report_problem_with_trip.with_raw_response.retrieve( + trip_id="tripID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + report_problem_with_trip = response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: + with client.report_problem_with_trip.with_streaming_response.retrieve( + trip_id="tripID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + report_problem_with_trip = response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `trip_id` but received ''"): + client.report_problem_with_trip.with_raw_response.retrieve( + trip_id="", + ) + + +class TestAsyncReportProblemWithTrip: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + report_problem_with_trip = await async_client.report_problem_with_trip.retrieve( + trip_id="tripID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + report_problem_with_trip = await async_client.report_problem_with_trip.retrieve( + trip_id="tripID", + code="vehicle_never_came", + service_date=0, + stop_id="stopID", + user_comment="userComment", + user_lat=0, + user_location_accuracy=0, + user_lon=0, + user_on_vehicle=True, + user_vehicle_number="userVehicleNumber", + vehicle_id="vehicleID", + ) + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.report_problem_with_trip.with_raw_response.retrieve( + trip_id="tripID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + report_problem_with_trip = await response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.report_problem_with_trip.with_streaming_response.retrieve( + trip_id="tripID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + report_problem_with_trip = await response.parse() + assert_matches_type(ResponseWrapper, report_problem_with_trip, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `trip_id` but received ''"): + await async_client.report_problem_with_trip.with_raw_response.retrieve( + trip_id="", + ) diff --git a/tests/api_resources/test_route.py b/tests/api_resources/test_route.py index 8b773cfe..8f52e08a 100644 --- a/tests/api_resources/test_route.py +++ b/tests/api_resources/test_route.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_route_ids_for_agency.py b/tests/api_resources/test_route_ids_for_agency.py new file mode 100644 index 00000000..df9b4b93 --- /dev/null +++ b/tests/api_resources/test_route_ids_for_agency.py @@ -0,0 +1,100 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import RouteIDsForAgencyListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRouteIDsForAgency: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + route_ids_for_agency = client.route_ids_for_agency.list( + "agencyID", + ) + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.route_ids_for_agency.with_raw_response.list( + "agencyID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + route_ids_for_agency = response.parse() + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.route_ids_for_agency.with_streaming_response.list( + "agencyID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + route_ids_for_agency = response.parse() + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + client.route_ids_for_agency.with_raw_response.list( + "", + ) + + +class TestAsyncRouteIDsForAgency: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + route_ids_for_agency = await async_client.route_ids_for_agency.list( + "agencyID", + ) + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.route_ids_for_agency.with_raw_response.list( + "agencyID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + route_ids_for_agency = await response.parse() + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.route_ids_for_agency.with_streaming_response.list( + "agencyID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + route_ids_for_agency = await response.parse() + assert_matches_type(RouteIDsForAgencyListResponse, route_ids_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + await async_client.route_ids_for_agency.with_raw_response.list( + "", + ) diff --git a/tests/api_resources/test_routes_for_agency.py b/tests/api_resources/test_routes_for_agency.py new file mode 100644 index 00000000..793d7bcf --- /dev/null +++ b/tests/api_resources/test_routes_for_agency.py @@ -0,0 +1,100 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import RoutesForAgencyListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoutesForAgency: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + routes_for_agency = client.routes_for_agency.list( + "40", + ) + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.routes_for_agency.with_raw_response.list( + "40", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + routes_for_agency = response.parse() + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.routes_for_agency.with_streaming_response.list( + "40", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + routes_for_agency = response.parse() + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + client.routes_for_agency.with_raw_response.list( + "", + ) + + +class TestAsyncRoutesForAgency: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + routes_for_agency = await async_client.routes_for_agency.list( + "40", + ) + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.routes_for_agency.with_raw_response.list( + "40", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + routes_for_agency = await response.parse() + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.routes_for_agency.with_streaming_response.list( + "40", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + routes_for_agency = await response.parse() + assert_matches_type(RoutesForAgencyListResponse, routes_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + await async_client.routes_for_agency.with_raw_response.list( + "", + ) diff --git a/tests/api_resources/test_routes_for_location.py b/tests/api_resources/test_routes_for_location.py new file mode 100644 index 00000000..fda2be85 --- /dev/null +++ b/tests/api_resources/test_routes_for_location.py @@ -0,0 +1,116 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import RoutesForLocationListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRoutesForLocation: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + routes_for_location = client.routes_for_location.list( + lat=0, + lon=0, + ) + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + routes_for_location = client.routes_for_location.list( + lat=0, + lon=0, + lat_span=0, + lon_span=0, + query="query", + radius=0, + ) + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.routes_for_location.with_raw_response.list( + lat=0, + lon=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + routes_for_location = response.parse() + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.routes_for_location.with_streaming_response.list( + lat=0, + lon=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + routes_for_location = response.parse() + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncRoutesForLocation: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + routes_for_location = await async_client.routes_for_location.list( + lat=0, + lon=0, + ) + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + routes_for_location = await async_client.routes_for_location.list( + lat=0, + lon=0, + lat_span=0, + lon_span=0, + query="query", + radius=0, + ) + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.routes_for_location.with_raw_response.list( + lat=0, + lon=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + routes_for_location = await response.parse() + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.routes_for_location.with_streaming_response.list( + lat=0, + lon=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + routes_for_location = await response.parse() + assert_matches_type(RoutesForLocationListResponse, routes_for_location, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_schedule_for_route.py b/tests/api_resources/test_schedule_for_route.py new file mode 100644 index 00000000..bd6f15fd --- /dev/null +++ b/tests/api_resources/test_schedule_for_route.py @@ -0,0 +1,117 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import ScheduleForRouteRetrieveResponse +from onebusaway._utils import parse_date + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestScheduleForRoute: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OnebusawaySDK) -> None: + schedule_for_route = client.schedule_for_route.retrieve( + route_id="1_100223", + ) + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: + schedule_for_route = client.schedule_for_route.retrieve( + route_id="1_100223", + date=parse_date("2019-12-27"), + ) + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: + response = client.schedule_for_route.with_raw_response.retrieve( + route_id="1_100223", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule_for_route = response.parse() + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: + with client.schedule_for_route.with_streaming_response.retrieve( + route_id="1_100223", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule_for_route = response.parse() + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `route_id` but received ''"): + client.schedule_for_route.with_raw_response.retrieve( + route_id="", + ) + + +class TestAsyncScheduleForRoute: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + schedule_for_route = await async_client.schedule_for_route.retrieve( + route_id="1_100223", + ) + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + schedule_for_route = await async_client.schedule_for_route.retrieve( + route_id="1_100223", + date=parse_date("2019-12-27"), + ) + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.schedule_for_route.with_raw_response.retrieve( + route_id="1_100223", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule_for_route = await response.parse() + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.schedule_for_route.with_streaming_response.retrieve( + route_id="1_100223", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule_for_route = await response.parse() + assert_matches_type(ScheduleForRouteRetrieveResponse, schedule_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `route_id` but received ''"): + await async_client.schedule_for_route.with_raw_response.retrieve( + route_id="", + ) diff --git a/tests/api_resources/test_schedule_for_stop.py b/tests/api_resources/test_schedule_for_stop.py index 0154e44f..2035e05c 100644 --- a/tests/api_resources/test_schedule_for_stop.py +++ b/tests/api_resources/test_schedule_for_stop.py @@ -66,7 +66,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncScheduleForStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_search_for_route.py b/tests/api_resources/test_search_for_route.py new file mode 100644 index 00000000..af87522a --- /dev/null +++ b/tests/api_resources/test_search_for_route.py @@ -0,0 +1,102 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import SearchForRouteListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSearchForRoute: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + search_for_route = client.search_for_route.list( + input="input", + ) + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + search_for_route = client.search_for_route.list( + input="input", + max_count=0, + ) + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.search_for_route.with_raw_response.list( + input="input", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search_for_route = response.parse() + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.search_for_route.with_streaming_response.list( + input="input", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search_for_route = response.parse() + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncSearchForRoute: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + search_for_route = await async_client.search_for_route.list( + input="input", + ) + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + search_for_route = await async_client.search_for_route.list( + input="input", + max_count=0, + ) + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.search_for_route.with_raw_response.list( + input="input", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search_for_route = await response.parse() + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.search_for_route.with_streaming_response.list( + input="input", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search_for_route = await response.parse() + assert_matches_type(SearchForRouteListResponse, search_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_search_for_stop.py b/tests/api_resources/test_search_for_stop.py new file mode 100644 index 00000000..d6357a65 --- /dev/null +++ b/tests/api_resources/test_search_for_stop.py @@ -0,0 +1,102 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import SearchForStopListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSearchForStop: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + search_for_stop = client.search_for_stop.list( + input="input", + ) + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + search_for_stop = client.search_for_stop.list( + input="input", + max_count=0, + ) + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.search_for_stop.with_raw_response.list( + input="input", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search_for_stop = response.parse() + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.search_for_stop.with_streaming_response.list( + input="input", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search_for_stop = response.parse() + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncSearchForStop: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + search_for_stop = await async_client.search_for_stop.list( + input="input", + ) + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + search_for_stop = await async_client.search_for_stop.list( + input="input", + max_count=0, + ) + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.search_for_stop.with_raw_response.list( + input="input", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search_for_stop = await response.parse() + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.search_for_stop.with_streaming_response.list( + input="input", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search_for_stop = await response.parse() + assert_matches_type(SearchForStopListResponse, search_for_stop, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_shape.py b/tests/api_resources/test_shape.py new file mode 100644 index 00000000..ec2c0310 --- /dev/null +++ b/tests/api_resources/test_shape.py @@ -0,0 +1,100 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import ShapeRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestShape: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: OnebusawaySDK) -> None: + shape = client.shape.retrieve( + "shapeID", + ) + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: + response = client.shape.with_raw_response.retrieve( + "shapeID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + shape = response.parse() + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: + with client.shape.with_streaming_response.retrieve( + "shapeID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + shape = response.parse() + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `shape_id` but received ''"): + client.shape.with_raw_response.retrieve( + "", + ) + + +class TestAsyncShape: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + shape = await async_client.shape.retrieve( + "shapeID", + ) + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.shape.with_raw_response.retrieve( + "shapeID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + shape = await response.parse() + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.shape.with_streaming_response.retrieve( + "shapeID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + shape = await response.parse() + assert_matches_type(ShapeRetrieveResponse, shape, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `shape_id` but received ''"): + await async_client.shape.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/test_stop.py b/tests/api_resources/test_stop.py index 92861763..bb895b37 100644 --- a/tests/api_resources/test_stop.py +++ b/tests/api_resources/test_stop.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncStop: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stop_ids_for_agency.py b/tests/api_resources/test_stop_ids_for_agency.py index 923a7439..d7d48fe7 100644 --- a/tests/api_resources/test_stop_ids_for_agency.py +++ b/tests/api_resources/test_stop_ids_for_agency.py @@ -57,7 +57,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopIDsForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_stops_for_agency.py b/tests/api_resources/test_stops_for_agency.py new file mode 100644 index 00000000..c54df148 --- /dev/null +++ b/tests/api_resources/test_stops_for_agency.py @@ -0,0 +1,100 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import StopsForAgencyListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestStopsForAgency: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + stops_for_agency = client.stops_for_agency.list( + "agencyID", + ) + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.stops_for_agency.with_raw_response.list( + "agencyID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stops_for_agency = response.parse() + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.stops_for_agency.with_streaming_response.list( + "agencyID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stops_for_agency = response.parse() + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + client.stops_for_agency.with_raw_response.list( + "", + ) + + +class TestAsyncStopsForAgency: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + stops_for_agency = await async_client.stops_for_agency.list( + "agencyID", + ) + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.stops_for_agency.with_raw_response.list( + "agencyID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stops_for_agency = await response.parse() + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.stops_for_agency.with_streaming_response.list( + "agencyID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stops_for_agency = await response.parse() + assert_matches_type(StopsForAgencyListResponse, stops_for_agency, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `agency_id` but received ''"): + await async_client.stops_for_agency.with_raw_response.list( + "", + ) diff --git a/tests/api_resources/test_stops_for_location.py b/tests/api_resources/test_stops_for_location.py index d1abaeb3..091e7884 100644 --- a/tests/api_resources/test_stops_for_location.py +++ b/tests/api_resources/test_stops_for_location.py @@ -9,7 +9,7 @@ from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK from tests.utils import assert_matches_type -from onebusaway.types import StopsForLocationRetrieveResponse +from onebusaway.types import StopsForLocationListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,71 +18,99 @@ class TestStopsForLocation: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: OnebusawaySDK) -> None: - stops_for_location = client.stops_for_location.retrieve() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + def test_method_list(self, client: OnebusawaySDK) -> None: + stops_for_location = client.stops_for_location.list( + lat=0, + lon=0, + ) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: - stops_for_location = client.stops_for_location.retrieve( + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + stops_for_location = client.stops_for_location.list( lat=0, lon=0, + lat_span=0, + lon_span=0, + query="query", + radius=0, ) - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: - response = client.stops_for_location.with_raw_response.retrieve() + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.stops_for_location.with_raw_response.list( + lat=0, + lon=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" stops_for_location = response.parse() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: - with client.stops_for_location.with_streaming_response.retrieve() as response: + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.stops_for_location.with_streaming_response.list( + lat=0, + lon=0, + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" stops_for_location = response.parse() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) assert cast(Any, response.is_closed) is True class TestAsyncStopsForLocation: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize - async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - stops_for_location = await async_client.stops_for_location.retrieve() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + stops_for_location = await async_client.stops_for_location.list( + lat=0, + lon=0, + ) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: - stops_for_location = await async_client.stops_for_location.retrieve( + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + stops_for_location = await async_client.stops_for_location.list( lat=0, lon=0, + lat_span=0, + lon_span=0, + query="query", + radius=0, ) - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - response = await async_client.stops_for_location.with_raw_response.retrieve() + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.stops_for_location.with_raw_response.list( + lat=0, + lon=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" stops_for_location = await response.parse() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - async with async_client.stops_for_location.with_streaming_response.retrieve() as response: + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.stops_for_location.with_streaming_response.list( + lat=0, + lon=0, + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" stops_for_location = await response.parse() - assert_matches_type(StopsForLocationRetrieveResponse, stops_for_location, path=["response"]) + assert_matches_type(StopsForLocationListResponse, stops_for_location, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_stops_for_route.py b/tests/api_resources/test_stops_for_route.py index e062f7d9..da8c3dd6 100644 --- a/tests/api_resources/test_stops_for_route.py +++ b/tests/api_resources/test_stops_for_route.py @@ -66,7 +66,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncStopsForRoute: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip.py b/tests/api_resources/test_trip.py index ef2bd7fc..3f2d1807 100644 --- a/tests/api_resources/test_trip.py +++ b/tests/api_resources/test_trip.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTrip: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip_details.py b/tests/api_resources/test_trip_details.py index 82d00d92..6041ff6d 100644 --- a/tests/api_resources/test_trip_details.py +++ b/tests/api_resources/test_trip_details.py @@ -69,7 +69,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTripDetails: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trip_for_vehicle.py b/tests/api_resources/test_trip_for_vehicle.py index be96fa6a..44981c23 100644 --- a/tests/api_resources/test_trip_for_vehicle.py +++ b/tests/api_resources/test_trip_for_vehicle.py @@ -68,7 +68,9 @@ def test_path_params_retrieve(self, client: OnebusawaySDK) -> None: class TestAsyncTripForVehicle: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/api_resources/test_trips_for_location.py b/tests/api_resources/test_trips_for_location.py index 439faca5..236a09c4 100644 --- a/tests/api_resources/test_trips_for_location.py +++ b/tests/api_resources/test_trips_for_location.py @@ -9,7 +9,7 @@ from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK from tests.utils import assert_matches_type -from onebusaway.types import TripsForLocationRetrieveResponse +from onebusaway.types import TripsForLocationListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -18,18 +18,18 @@ class TestTripsForLocation: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_retrieve(self, client: OnebusawaySDK) -> None: - trips_for_location = client.trips_for_location.retrieve( + def test_method_list(self, client: OnebusawaySDK) -> None: + trips_for_location = client.trips_for_location.list( lat=0, lat_span=0, lon=0, lon_span=0, ) - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: - trips_for_location = client.trips_for_location.retrieve( + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + trips_for_location = client.trips_for_location.list( lat=0, lat_span=0, lon=0, @@ -38,11 +38,11 @@ def test_method_retrieve_with_all_params(self, client: OnebusawaySDK) -> None: include_trip=True, time=0, ) - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: - response = client.trips_for_location.with_raw_response.retrieve( + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.trips_for_location.with_raw_response.list( lat=0, lat_span=0, lon=0, @@ -52,11 +52,11 @@ def test_raw_response_retrieve(self, client: OnebusawaySDK) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" trips_for_location = response.parse() - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: - with client.trips_for_location.with_streaming_response.retrieve( + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.trips_for_location.with_streaming_response.list( lat=0, lat_span=0, lon=0, @@ -66,27 +66,29 @@ def test_streaming_response_retrieve(self, client: OnebusawaySDK) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" trips_for_location = response.parse() - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) assert cast(Any, response.is_closed) is True class TestAsyncTripsForLocation: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize - async def test_method_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - trips_for_location = await async_client.trips_for_location.retrieve( + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + trips_for_location = await async_client.trips_for_location.list( lat=0, lat_span=0, lon=0, lon_span=0, ) - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: - trips_for_location = await async_client.trips_for_location.retrieve( + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + trips_for_location = await async_client.trips_for_location.list( lat=0, lat_span=0, lon=0, @@ -95,11 +97,11 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncOnebusaw include_trip=True, time=0, ) - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - response = await async_client.trips_for_location.with_raw_response.retrieve( + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.trips_for_location.with_raw_response.list( lat=0, lat_span=0, lon=0, @@ -109,11 +111,11 @@ async def test_raw_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" trips_for_location = await response.parse() - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySDK) -> None: - async with async_client.trips_for_location.with_streaming_response.retrieve( + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.trips_for_location.with_streaming_response.list( lat=0, lat_span=0, lon=0, @@ -123,6 +125,6 @@ async def test_streaming_response_retrieve(self, async_client: AsyncOnebusawaySD assert response.http_request.headers.get("X-Stainless-Lang") == "python" trips_for_location = await response.parse() - assert_matches_type(TripsForLocationRetrieveResponse, trips_for_location, path=["response"]) + assert_matches_type(TripsForLocationListResponse, trips_for_location, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_trips_for_route.py b/tests/api_resources/test_trips_for_route.py new file mode 100644 index 00000000..c708aab8 --- /dev/null +++ b/tests/api_resources/test_trips_for_route.py @@ -0,0 +1,120 @@ +# 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 onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from tests.utils import assert_matches_type +from onebusaway.types import TripsForRouteListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestTripsForRoute: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: OnebusawaySDK) -> None: + trips_for_route = client.trips_for_route.list( + route_id="routeID", + ) + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OnebusawaySDK) -> None: + trips_for_route = client.trips_for_route.list( + route_id="routeID", + include_schedule=True, + include_status=True, + time=0, + ) + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OnebusawaySDK) -> None: + response = client.trips_for_route.with_raw_response.list( + route_id="routeID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + trips_for_route = response.parse() + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OnebusawaySDK) -> None: + with client.trips_for_route.with_streaming_response.list( + route_id="routeID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + trips_for_route = response.parse() + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: OnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `route_id` but received ''"): + client.trips_for_route.with_raw_response.list( + route_id="", + ) + + +class TestAsyncTripsForRoute: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: + trips_for_route = await async_client.trips_for_route.list( + route_id="routeID", + ) + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOnebusawaySDK) -> None: + trips_for_route = await async_client.trips_for_route.list( + route_id="routeID", + include_schedule=True, + include_status=True, + time=0, + ) + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + response = await async_client.trips_for_route.with_raw_response.list( + route_id="routeID", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + trips_for_route = await response.parse() + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOnebusawaySDK) -> None: + async with async_client.trips_for_route.with_streaming_response.list( + route_id="routeID", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + trips_for_route = await response.parse() + assert_matches_type(TripsForRouteListResponse, trips_for_route, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncOnebusawaySDK) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `route_id` but received ''"): + await async_client.trips_for_route.with_raw_response.list( + route_id="", + ) diff --git a/tests/api_resources/test_vehicles_for_agency.py b/tests/api_resources/test_vehicles_for_agency.py index 5f201f22..88e709c6 100644 --- a/tests/api_resources/test_vehicles_for_agency.py +++ b/tests/api_resources/test_vehicles_for_agency.py @@ -65,7 +65,9 @@ def test_path_params_list(self, client: OnebusawaySDK) -> None: class TestAsyncVehiclesForAgency: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncOnebusawaySDK) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 9be801df..dd1366e0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,27 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os -import asyncio import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest +from pytest_asyncio import is_async_test -from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK +from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK, DefaultAioHttpClient +from onebusaway._utils import is_dict if TYPE_CHECKING: - from _pytest.fixtures import FixtureRequest + from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] pytest.register_assert_rewrite("tests.utils") logging.getLogger("onebusaway").setLevel(logging.DEBUG) -@pytest.fixture(scope="session") -def event_loop() -> Iterator[asyncio.AbstractEventLoop]: - loop = asyncio.new_event_loop() - yield loop - loop.close() +# automatically add `pytest.mark.asyncio()` to all of our async tests +# so we don't have to add that boilerplate everywhere +def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: + pytest_asyncio_tests = (item for item in items if is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + 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") @@ -41,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[OnebusawaySDK]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOnebusawaySDK]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + 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 AsyncOnebusawaySDK( + base_url=base_url, api_key=api_key, _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 a099efed..0940f79f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,15 +2,18 @@ from __future__ import annotations -# import gc +import gc import os +import sys import json import asyncio import inspect - -# import tracemalloc -from typing import Any, Union, cast +import dataclasses +import tracemalloc +import urllib.parse +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -18,18 +21,24 @@ from pydantic import ValidationError from onebusaway import OnebusawaySDK, AsyncOnebusawaySDK, APIResponseValidationError +from onebusaway._types import Omit +from onebusaway._utils import asyncify from onebusaway._models import BaseModel, FinalRequestOptions -from onebusaway._constants import RAW_RESPONSE_HEADER from onebusaway._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from onebusaway._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") api_key = "My API Key" @@ -44,6 +53,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +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 + + def _get_open_connections(client: OnebusawaySDK | AsyncOnebusawaySDK) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -53,51 +113,49 @@ def _get_open_connections(client: OnebusawaySDK | AsyncOnebusawaySDK) -> int: class TestOnebusawaySDK: - client = OnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @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: OnebusawaySDK) -> 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: OnebusawaySDK) -> 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: OnebusawaySDK) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(api_key="another My API Key") + copied = client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: OnebusawaySDK) -> 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 = OnebusawaySDK( @@ -132,6 +190,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 = OnebusawaySDK( @@ -169,13 +228,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar", "key": api_key}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: OnebusawaySDK) -> 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(): @@ -185,76 +246,75 @@ 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: - # options = FinalRequestOptions(method="get", url="/foo") - - # def build_request(options: FinalRequestOptions) -> None: - # client = self.client.copy() - # client._build_request(options) - - # # ensure that the machinery is warmed up before tracing starts. - # build_request(options) - # gc.collect() - - # tracemalloc.start(1000) - - # snapshot_before = tracemalloc.take_snapshot() - - # ITERATIONS = 10 - # for _ in range(ITERATIONS): - # build_request(options) - - # gc.collect() - # snapshot_after = tracemalloc.take_snapshot() - - # tracemalloc.stop() - - # def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: - # if diff.count == 0: - # # Avoid false positives by considering only leaks (i.e. allocations that persist). - # return - - # if diff.count % ITERATIONS != 0: - # # Avoid false positives by considering only leaks that appear per iteration. - # return - - # for frame in diff.traceback: - # if any( - # frame.filename.endswith(fragment) - # for fragment in [ - # # to_raw_response_wrapper leaks through the @functools.wraps() decorator. - # # - # # removing the decorator fixes the leak for reasons we don't understand. - # "onebusaway/_legacy_response.py", - # "onebusaway/_response.py", - # # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. - # "onebusaway/_compat.py", - # # Standard library leaks we don't care about. - # "/logging/__init__.py", - # ] - # ): - # return - - # leaks.append(diff) - - # leaks: list[tracemalloc.StatisticDiff] = [] - # for diff in snapshot_after.compare_to(snapshot_before, "traceback"): - # add_leak(leaks, diff) - # if leaks: - # for leak in leaks: - # print("MEMORY LEAK:", leak) - # for frame in leak.traceback: - # print(frame) - # raise AssertionError() - - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + @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: OnebusawaySDK) -> None: + options = FinalRequestOptions(method="get", url="/foo") + + def build_request(options: FinalRequestOptions) -> None: + client_copy = client.copy() + client_copy._build_request(options) + + # ensure that the machinery is warmed up before tracing starts. + build_request(options) + gc.collect() + + tracemalloc.start(1000) + + snapshot_before = tracemalloc.take_snapshot() + + ITERATIONS = 10 + for _ in range(ITERATIONS): + build_request(options) + + gc.collect() + snapshot_after = tracemalloc.take_snapshot() + + tracemalloc.stop() + + def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: + if diff.count == 0: + # Avoid false positives by considering only leaks (i.e. allocations that persist). + return + + if diff.count % ITERATIONS != 0: + # Avoid false positives by considering only leaks that appear per iteration. + return + + for frame in diff.traceback: + if any( + frame.filename.endswith(fragment) + for fragment in [ + # to_raw_response_wrapper leaks through the @functools.wraps() decorator. + # + # removing the decorator fixes the leak for reasons we don't understand. + "onebusaway/_legacy_response.py", + "onebusaway/_response.py", + # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. + "onebusaway/_compat.py", + # Standard library leaks we don't care about. + "/logging/__init__.py", + ] + ): + return + + leaks.append(diff) + + leaks: list[tracemalloc.StatisticDiff] = [] + for diff in snapshot_after.compare_to(snapshot_before, "traceback"): + add_leak(leaks, diff) + if leaks: + for leak in leaks: + print("MEMORY LEAK:", leak) + for frame in leak.traceback: + print(frame) + raise AssertionError() + + def test_request_timeout(self, client: OnebusawaySDK) -> 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) @@ -267,6 +327,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: @@ -278,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 = OnebusawaySDK( @@ -288,6 +352,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 = OnebusawaySDK( @@ -298,6 +364,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: @@ -309,14 +377,14 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - client = OnebusawaySDK( + test_client = OnebusawaySDK( base_url=base_url, api_key=api_key, _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 = OnebusawaySDK( + test_client2 = OnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -325,10 +393,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_default_query_option(self) -> None: client = OnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} @@ -341,14 +412,16 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden", "key": api_key} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden", "key": api_key} + + client.close() - def test_request_extra_json(self) -> None: - request = self.client._build_request( + def test_request_extra_json(self, client: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -359,7 +432,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", @@ -370,7 +443,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", @@ -381,8 +454,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: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -392,7 +465,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", @@ -403,8 +476,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: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -417,7 +490,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo", "key": api_key} # 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", @@ -431,7 +504,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2", "key": api_key} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -447,7 +520,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: OnebusawaySDK) -> 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"]}, @@ -474,7 +547,71 @@ def test_multipart_repeating_array(self, client: OnebusawaySDK) -> 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: OnebusawaySDK) -> 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 OnebusawaySDK( + base_url=base_url, + api_key=api_key, + _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: OnebusawaySDK) -> 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: OnebusawaySDK) -> None: class Model1(BaseModel): name: str @@ -483,12 +620,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: OnebusawaySDK) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -499,18 +636,20 @@ 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: OnebusawaySDK + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -526,7 +665,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 @@ -540,6 +679,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(ONEBUSAWAY_SDK_BASE_URL="http://localhost:5000/from/env"): client = OnebusawaySDK(api_key=api_key, _strict_response_validation=True) @@ -568,8 +709,9 @@ def test_base_url_trailing_slash(self, client: OnebusawaySDK) -> None: json_data={"foo": "bar"}, ), ) - expected_url = f"http://localhost:5000/custom/path/foo?key={client.api_key}" + expected_url = f"http://localhost:5000/custom/path/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == expected_url + client.close() @pytest.mark.parametrize( "client", @@ -594,8 +736,9 @@ def test_base_url_no_trailing_slash(self, client: OnebusawaySDK) -> None: json_data={"foo": "bar"}, ), ) - expected_url = f"http://localhost:5000/custom/path/foo?key={client.api_key}" + expected_url = f"http://localhost:5000/custom/path/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == expected_url + client.close() @pytest.mark.parametrize( "client", @@ -620,37 +763,38 @@ def test_absolute_request_url(self, client: OnebusawaySDK) -> None: json_data={"foo": "bar"}, ), ) - expected_url = f"https://myapi.com/foo?key={client.api_key}" + expected_url = f"https://myapi.com/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == expected_url + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = OnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = OnebusawaySDK(base_url=base_url, api_key=api_key, _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 = OnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = OnebusawaySDK(base_url=base_url, api_key=api_key, _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: OnebusawaySDK) -> 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) @@ -672,11 +816,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = OnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = OnebusawaySDK(base_url=base_url, api_key=api_key, _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", [ @@ -695,12 +842,13 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @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 = OnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: OnebusawaySDK + ) -> 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) @@ -708,83 +856,202 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: OnebusawaySDK) -> None: respx_mock.get("/api/where/current-time.json").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.get( - "/api/where/current-time.json", - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.current_time.with_streaming_response.retrieve().__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: OnebusawaySDK) -> None: respx_mock.get("/api/where/current-time.json").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.get( - "/api/where/current-time.json", - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.current_time.with_streaming_response.retrieve().__enter__() + assert _get_open_connections(client) == 0 - assert _get_open_connections(self.client) == 0 + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + def test_retries_taken( + self, + client: OnebusawaySDK, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = client.current_time.with_raw_response.retrieve() + + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_omit_retry_count_header( + self, client: OnebusawaySDK, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + nb_retries = 0 -class TestAsyncOnebusawaySDK: - client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = client.current_time.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": Omit()}) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_overwrite_retry_count_header( + self, client: OnebusawaySDK, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = client.current_time.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": "42"}) + + 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: OnebusawaySDK) -> 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) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: OnebusawaySDK) -> 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 TestAsyncOnebusawaySDK: + @pytest.mark.respx(base_url=base_url) + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncOnebusawaySDK) -> 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: AsyncOnebusawaySDK) -> 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: AsyncOnebusawaySDK) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(api_key="another My API Key") + copied = async_client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert async_client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncOnebusawaySDK) -> 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 = AsyncOnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) @@ -817,8 +1084,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 = AsyncOnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) @@ -854,13 +1122,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: AsyncOnebusawaySDK) -> 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(): @@ -870,74 +1140,74 @@ 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: - # options = FinalRequestOptions(method="get", url="/foo") - - # def build_request(options: FinalRequestOptions) -> None: - # client = self.client.copy() - # client._build_request(options) - - # ensure that the machinery is warmed up before tracing starts. - # build_request(options) - # gc.collect() - - # tracemalloc.start(1000) - - # snapshot_before = tracemalloc.take_snapshot() - - # ITERATIONS = 10 - # for _ in range(ITERATIONS): - # build_request(options) - - # gc.collect() - # snapshot_after = tracemalloc.take_snapshot() - - # tracemalloc.stop() - - # def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: - # if diff.count == 0: - # Avoid false positives by considering only leaks (i.e. allocations that persist). - # return - - # if diff.count % ITERATIONS != 0: - # Avoid false positives by considering only leaks that appear per iteration. - # return - - # for frame in diff.traceback: - # if any( - # frame.filename.endswith(fragment) - # for fragment in [ - # to_raw_response_wrapper leaks through the @functools.wraps() decorator. - - # removing the decorator fixes the leak for reasons we don't understand. - # "onebusaway/_legacy_response.py", - # "onebusaway/_response.py", - # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. - # "onebusaway/_compat.py", - # Standard library leaks we don't care about. - # "/logging/__init__.py", - # ] - # ): - # return - - # leaks.append(diff) - - # leaks: list[tracemalloc.StatisticDiff] = [] - # for diff in snapshot_after.compare_to(snapshot_before, "traceback"): - # add_leak(leaks, diff) - # if leaks: - # for leak in leaks: - # print("MEMORY LEAK:", leak) - # for frame in leak.traceback: - # print(frame) - # raise AssertionError() - - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + @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: AsyncOnebusawaySDK) -> None: + options = FinalRequestOptions(method="get", url="/foo") + + def build_request(options: FinalRequestOptions) -> None: + client_copy = async_client.copy() + client_copy._build_request(options) + + # ensure that the machinery is warmed up before tracing starts. + build_request(options) + gc.collect() + + tracemalloc.start(1000) + + snapshot_before = tracemalloc.take_snapshot() + + ITERATIONS = 10 + for _ in range(ITERATIONS): + build_request(options) + + gc.collect() + snapshot_after = tracemalloc.take_snapshot() + + tracemalloc.stop() + + def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: + if diff.count == 0: + # Avoid false positives by considering only leaks (i.e. allocations that persist). + return + + if diff.count % ITERATIONS != 0: + # Avoid false positives by considering only leaks that appear per iteration. + return + + for frame in diff.traceback: + if any( + frame.filename.endswith(fragment) + for fragment in [ + # to_raw_response_wrapper leaks through the @functools.wraps() decorator. + # removing the decorator fixes the leak for reasons we don't understand. + "onebusaway/_legacy_response.py", + "onebusaway/_response.py", + # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. + "onebusaway/_compat.py", + # Standard library leaks we don't care about. + "/logging/__init__.py", + ] + ): + return + + leaks.append(diff) + + leaks: list[tracemalloc.StatisticDiff] = [] + for diff in snapshot_after.compare_to(snapshot_before, "traceback"): + add_leak(leaks, diff) + if leaks: + for leak in leaks: + print("MEMORY LEAK:", leak) + for frame in leak.traceback: + print(frame) + raise AssertionError() + + async def test_request_timeout(self, async_client: AsyncOnebusawaySDK) -> 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 @@ -952,6 +1222,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: @@ -963,6 +1235,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 = AsyncOnebusawaySDK( @@ -973,6 +1247,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 = AsyncOnebusawaySDK( @@ -983,6 +1259,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: @@ -993,15 +1271,15 @@ def test_invalid_http_client(self) -> None: http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncOnebusawaySDK( + async def test_default_headers_option(self) -> None: + test_client = AsyncOnebusawaySDK( base_url=base_url, api_key=api_key, _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 = AsyncOnebusawaySDK( + test_client2 = AsyncOnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1010,11 +1288,14 @@ 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" - def test_default_query_option(self) -> None: + await test_client.close() + await test_client2.close() + + async def test_default_query_option(self) -> None: client = AsyncOnebusawaySDK( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) @@ -1027,14 +1308,14 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden", "key": api_key}, + params={"foo": "baz", "query_param": "overridden", "key": api_key}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden", "key": api_key} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden", "key": api_key} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + def test_request_extra_json(self, client: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1045,7 +1326,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", @@ -1056,7 +1337,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", @@ -1067,8 +1348,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: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1078,7 +1359,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", @@ -1089,8 +1370,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: OnebusawaySDK) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1103,7 +1384,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo", "key": api_key} # 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", @@ -1116,7 +1397,7 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"bar": "1", "foo": "2", "key": api_key} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1132,7 +1413,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncOnebusawaySDK) -> 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"]}, @@ -1159,7 +1440,73 @@ def test_multipart_repeating_array(self, async_client: AsyncOnebusawaySDK) -> No ] @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: AsyncOnebusawaySDK) -> 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 AsyncOnebusawaySDK( + base_url=base_url, + api_key=api_key, + _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: AsyncOnebusawaySDK + ) -> 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: AsyncOnebusawaySDK) -> None: class Model1(BaseModel): name: str @@ -1168,12 +1515,14 @@ 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: AsyncOnebusawaySDK + ) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1184,18 +1533,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: AsyncOnebusawaySDK + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1211,11 +1562,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 = AsyncOnebusawaySDK( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) @@ -1225,7 +1576,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(ONEBUSAWAY_SDK_BASE_URL="http://localhost:5000/from/env"): client = AsyncOnebusawaySDK(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1245,7 +1598,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: + async def test_base_url_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1253,8 +1606,9 @@ def test_base_url_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: json_data={"foo": "bar"}, ), ) - excepted_url = f"http://localhost:5000/custom/path/foo?key={client.api_key}" + excepted_url = f"http://localhost:5000/custom/path/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == excepted_url + await client.close() @pytest.mark.parametrize( "client", @@ -1271,7 +1625,7 @@ def test_base_url_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1280,8 +1634,9 @@ def test_base_url_no_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: ), ) - expected_url = f"http://localhost:5000/custom/path/foo?key={client.api_key}" + expected_url = f"http://localhost:5000/custom/path/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == expected_url + await client.close() @pytest.mark.parametrize( "client", @@ -1298,7 +1653,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncOnebusawaySDK) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncOnebusawaySDK) -> None: + async def test_absolute_request_url(self, client: AsyncOnebusawaySDK) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1306,39 +1661,41 @@ def test_absolute_request_url(self, client: AsyncOnebusawaySDK) -> None: json_data={"foo": "bar"}, ), ) - expected_url = f"https://myapi.com/foo?key={client.api_key}" + expected_url = f"https://myapi.com/foo?key={urllib.parse.quote_plus(client.api_key)}" assert request.url == expected_url + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _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 = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _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: AsyncOnebusawaySDK + ) -> 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) @@ -1349,7 +1706,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 @@ -1361,11 +1717,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _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", [ @@ -1384,42 +1743,177 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 8], # test large number potentially overflowing ], ) @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 = AsyncOnebusawaySDK(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncOnebusawaySDK + ) -> 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] @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_timeout_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncOnebusawaySDK + ) -> None: respx_mock.get("/api/where/current-time.json").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.get( - "/api/where/current-time.json", - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.current_time.with_streaming_response.retrieve().__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_status_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncOnebusawaySDK + ) -> None: respx_mock.get("/api/where/current-time.json").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.get( - "/api/where/current-time.json", - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + await async_client.current_time.with_streaming_response.retrieve().__aenter__() + assert _get_open_connections(async_client) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + @pytest.mark.parametrize("failure_mode", ["status", "exception"]) + async def test_retries_taken( + self, + async_client: AsyncOnebusawaySDK, + failures_before_success: int, + failure_mode: Literal["status", "exception"], + respx_mock: MockRouter, + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + if failure_mode == "exception": + raise RuntimeError("oops") + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = await client.current_time.with_raw_response.retrieve() + + assert response.retries_taken == failures_before_success + assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_omit_retry_count_header( + self, async_client: AsyncOnebusawaySDK, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = await client.current_time.with_raw_response.retrieve( + extra_headers={"x-stainless-retry-count": Omit()} + ) + + assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 + + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) + @mock.patch("onebusaway._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_overwrite_retry_count_header( + self, async_client: AsyncOnebusawaySDK, failures_before_success: int, respx_mock: MockRouter + ) -> None: + client = async_client.with_options(max_retries=4) + + nb_retries = 0 + + def retry_handler(_request: httpx.Request) -> httpx.Response: + nonlocal nb_retries + if nb_retries < failures_before_success: + nb_retries += 1 + return httpx.Response(500) + return httpx.Response(200) + + respx_mock.get("/api/where/current-time.json").mock(side_effect=retry_handler) + + response = await client.current_time.with_raw_response.retrieve(extra_headers={"x-stainless-retry-count": "42"}) + + assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + 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: AsyncOnebusawaySDK) -> 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: AsyncOnebusawaySDK) -> 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 _get_open_connections(self.client) == 0 + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py index 519c9b14..5da235a6 100644 --- a/tests/test_deepcopy.py +++ b/tests/test_deepcopy.py @@ -41,8 +41,7 @@ def test_nested_list() -> None: assert_different_identities(obj1[1], obj2[1]) -class MyObject: - ... +class MyObject: ... def test_ignores_other_types() -> None: diff --git a/tests/test_models.py b/tests/test_models.py index e2104937..f73d5e12 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,15 +1,15 @@ 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 +from typing_extensions import Literal, Annotated, TypeAliasType import pytest import pydantic from pydantic import Field from onebusaway._utils import PropertyInfo -from onebusaway._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from onebusaway._models import BaseModel, construct_type +from onebusaway._compat import PYDANTIC_V1, parse_obj, model_dump, model_json +from onebusaway._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -245,7 +245,7 @@ class Model(BaseModel): assert m.foo is True m = Model.construct(foo="CARD_HOLDER") - assert m.foo is "CARD_HOLDER" + assert m.foo == "CARD_HOLDER" m = Model.construct(foo={"bar": False}) assert isinstance(m.foo, Submodel1) @@ -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 @@ -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 @@ -520,19 +523,15 @@ class Model(BaseModel): assert m3.to_dict(exclude_none=True) == {} assert m3.to_dict(exclude_defaults=True) == {} - if PYDANTIC_V2: + class Model2(BaseModel): + created_at: datetime - class Model2(BaseModel): - created_at: datetime - - time_str = "2024-03-21T11:39:01.275859" - m4 = Model2.construct(created_at=time_str) - assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} - assert m4.to_dict(mode="json") == {"created_at": time_str} - else: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.to_dict(mode="json") + time_str = "2024-03-21T11:39:01.275859" + m4 = Model2.construct(created_at=time_str) + assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} + assert m4.to_dict(mode="json") == {"created_at": time_str} + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -557,10 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.model_dump(mode="json") - + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -568,6 +564,14 @@ class Model(BaseModel): m.model_dump(warnings=False) +def test_compat_method_no_error_for_warnings() -> None: + class Model(BaseModel): + foo: Optional[str] + + m = Model(foo="hello") + assert isinstance(model_dump(m, warnings=False), dict) + + def test_to_json() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) @@ -576,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()) == {} @@ -591,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) @@ -618,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) @@ -675,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: @@ -764,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: @@ -805,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")]) @@ -814,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( @@ -826,4 +830,134 @@ 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(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) # pyright: ignore + + class Model(BaseModel): + alias: Alias + union: Union[int, Alias] + + m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.alias, str) + assert m.alias == "foo" + assert isinstance(m.union, str) + assert m.union == "bar" + + +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") +def test_field_named_cls() -> None: + class Model(BaseModel): + cls: str + + m = construct_type(value={"cls": "foo"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.cls, str) + + +def test_discriminated_union_case() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["b"] + + data: List[Union[A, object]] + + class ModelA(BaseModel): + type: Literal["modelA"] + + data: int + + class ModelB(BaseModel): + type: Literal["modelB"] + + required: str + + data: Union[A, B] + + # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` + m = construct_type( + value={"type": "modelB", "data": {"type": "a", "data": True}}, + type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), + ) + + 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_response.py b/tests/test_response.py index 9e02d05c..25c4e74c 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,5 +1,5 @@ import json -from typing import List, cast +from typing import Any, List, Union, cast from typing_extensions import Annotated import httpx @@ -19,16 +19,13 @@ from onebusaway._base_client import FinalRequestOptions -class ConcreteBaseAPIResponse(APIResponse[bytes]): - ... +class ConcreteBaseAPIResponse(APIResponse[bytes]): ... -class ConcreteAPIResponse(APIResponse[List[str]]): - ... +class ConcreteAPIResponse(APIResponse[List[str]]): ... -class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): - ... +class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ... def test_extract_response_type_direct_classes() -> None: @@ -56,8 +53,7 @@ def test_extract_response_type_binary_response() -> None: assert extract_response_type(AsyncBinaryAPIResponse) == bytes -class PydanticModel(pydantic.BaseModel): - ... +class PydanticModel(pydantic.BaseModel): ... def test_response_parse_mismatched_basemodel(client: OnebusawaySDK) -> None: @@ -192,3 +188,90 @@ async def test_async_response_parse_annotated_type(async_client: AsyncOnebusaway ) assert obj.foo == "hello!" assert obj.bar == 2 + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: OnebusawaySDK, content: str, expected: bool) -> None: + response = APIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +async def test_async_response_parse_bool(client: AsyncOnebusawaySDK, content: str, expected: bool) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = await response.parse(to=bool) + assert result is expected + + +class OtherModel(BaseModel): + a: str + + +@pytest.mark.parametrize("client", [False], indirect=True) # loose validation +def test_response_parse_expect_model_union_non_json_content(client: OnebusawaySDK) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel])) + assert isinstance(obj, str) + assert obj == "foo" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncOnebusawaySDK) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel])) + assert isinstance(obj, str) + assert obj == "foo" diff --git a/tests/test_transform.py b/tests/test_transform.py index 1f3bbddc..5615f5fe 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -2,20 +2,20 @@ import io import pathlib -from typing import Any, List, Union, TypeVar, Iterable, Optional, cast +from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest -from onebusaway._types import Base64FileInput +from onebusaway._types import Base64FileInput, omit, not_given from onebusaway._utils import ( PropertyInfo, transform as _transform, parse_datetime, async_transform as _async_transform, ) -from onebusaway._compat import PYDANTIC_V2 +from onebusaway._compat import PYDANTIC_V1 from onebusaway._models import BaseModel _T = TypeVar("_T") @@ -177,17 +177,32 @@ class DateDict(TypedDict, total=False): foo: Annotated[date, PropertyInfo(format="iso8601")] +class DatetimeModel(BaseModel): + foo: datetime + + +class DateModel(BaseModel): + foo: Optional[date] + + @parametrize @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+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] dt = dt.replace(tzinfo=None) assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == { + "foo": "2023-02-23" + } # type: ignore[comparison-overlap] @parametrize @@ -282,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} @@ -294,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"}} @@ -373,6 +388,15 @@ def my_iter() -> Iterable[Baz8]: } +@parametrize +@pytest.mark.asyncio +async def test_dictionary_items(use_async: bool) -> None: + class DictItems(TypedDict): + foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] + + assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}} + + class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] @@ -408,3 +432,29 @@ async def test_base64_file_input(use_async: bool) -> None: assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { "foo": "SGVsbG8sIHdvcmxkIQ==" } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@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) == {} + + +@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..a069595b --- /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 onebusaway._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..a8c33249 --- /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 onebusaway import _compat +from onebusaway._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_path.py b/tests/test_utils/test_path.py new file mode 100644 index 00000000..5cc6b87d --- /dev/null +++ b/tests/test_utils/test_path.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from onebusaway._utils._path import path_template + + +@pytest.mark.parametrize( + "template, kwargs, expected", + [ + ("/v1/{id}", dict(id="abc"), "/v1/abc"), + ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), + ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), + ("/{w}/{w}", dict(w="echo"), "/echo/echo"), + ("/v1/static", {}, "/v1/static"), + ("", {}, ""), + ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), + ("/v1/{v}", dict(v=None), "/v1/null"), + ("/v1/{v}", dict(v=True), "/v1/true"), + ("/v1/{v}", dict(v=False), "/v1/false"), + ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok + ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok + ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok + ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok + ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine + ( + "/v1/{a}?query={b}", + dict(a="../../other/endpoint", b="a&bad=true"), + "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", + ), + ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), + ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), + ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), + ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input + # Query: slash and ? are safe, # is not + ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), + ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), + ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), + ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), + # Fragment: slash and ? are safe + ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), + ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), + # Path: slash, ? and # are all encoded + ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), + ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), + ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), + # same var encoded differently by component + ( + "/v1/{v}?q={v}#{v}", + dict(v="a/b?c#d"), + "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", + ), + ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection + ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection + ], +) +def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: + assert path_template(template, **kwargs) == expected + + +def test_missing_kwarg_raises_key_error() -> None: + with pytest.raises(KeyError, match="org_id"): + path_template("/v1/{org_id}") + + +@pytest.mark.parametrize( + "template, kwargs", + [ + ("{a}/path", dict(a=".")), + ("{a}/path", dict(a="..")), + ("/v1/{a}", dict(a=".")), + ("/v1/{a}", dict(a="..")), + ("/v1/{a}/path", dict(a=".")), + ("/v1/{a}/path", dict(a="..")), + ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." + ("/v1/{a}.", dict(a=".")), # var + static → ".." + ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." + ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text + ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/{v}?q=1", dict(v="..")), + ("/v1/{v}#frag", dict(v="..")), + ], +) +def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: + with pytest.raises(ValueError, match="dot-segment"): + path_template(template, **kwargs) diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 4778a91f..3cefe3fb 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/test_utils/test_typing.py b/tests/test_utils/test_typing.py index c6438ff0..1f6e80a7 100644 --- a/tests/test_utils/test_typing.py +++ b/tests/test_utils/test_typing.py @@ -9,24 +9,19 @@ _T3 = TypeVar("_T3") -class BaseGeneric(Generic[_T]): - ... +class BaseGeneric(Generic[_T]): ... -class SubclassGeneric(BaseGeneric[_T]): - ... +class SubclassGeneric(BaseGeneric[_T]): ... -class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): - ... +class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ... -class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): - ... +class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ... -class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): - ... +class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ... def test_extract_type_var() -> None: diff --git a/tests/utils.py b/tests/utils.py index d274fec1..d7fa7470 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,20 +4,22 @@ 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 -from onebusaway._types import NoneType +from onebusaway._types import Omit, NoneType from onebusaway._utils import ( is_dict, is_list, is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, + is_type_alias_type, ) -from onebusaway._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from onebusaway._compat import PYDANTIC_V1, field_outer_type, get_model_fields from onebusaway._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -26,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), @@ -51,6 +53,9 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + if is_type_alias_type(type_): + type_ = type_.__value__ + # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): type_ = extract_type_arg(type_, 0) @@ -67,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: @@ -139,11 +151,15 @@ def _assert_list_type(type_: type[object], value: object) -> None: @contextlib.contextmanager -def update_env(**new_env: str) -> Iterator[None]: +def update_env(**new_env: str | Omit) -> Iterator[None]: old = os.environ.copy() try: - os.environ.update(new_env) + for name, value in new_env.items(): + if isinstance(value, Omit): + os.environ.pop(name, None) + else: + os.environ[name] = value yield None finally: